PostCSS Deep Dive: Roll Your Own Preprocessor

来源:转载

In the previous tutorialwe went through how to use the excellent preprocessing pack “PreCSS”. In this tutorial we’ll be approaching PostCSS-based preprocessing in a different way; installing a hand-picked selection of plugins to custom build ourpreprocessor from the ground up.

I'm going to take you through the setup of what I personallyfind to be a great mix oflanguage extensionplugins. But when it comes to you rolling your own preprocessor you might choose to use only some of the plugins we cover here, or you might choose none at all, instead going withother options.

That’s the beauty of this process; you can have your preprocessor setup however you choose. The purpose of this tutorial will be to give you hands on experience of putting together a PostCSS preprocessor, and to fill you in on thefeatures of the currently available plugins so you can decide for yourself which you want to use.

Let's begin!

Setup Your Project

The first thing you’ll need to do is setup your project to use either Gulp or Grunt, depending on your preference. If you don’t already have a preference for one or the other I recommend using Gulp as you’ll need less code to achieve the same ends.

You can read about how to setup Gulp or Grunt projects for PostCSS in the previous tutorials

PostCSS Quickstart Guide: Gulp Setupor PostCSS Quickstart Guide: Grunt Setup

respectively.

If you don't want to manually setup your project from scratch though, you can download the source files attached to this tutorial,and extracteither the provided Gulp or Gruntstarter project into an empty project folder.

Then, with a terminal or command prompt pointed at the folder, run the command npm install.

Note on Plugin Installation

This tutorial will assume you‘ve followed the previous entries in the series and are now familiar with how to install a plugin into your project and load it via your Gulpfile or Gruntfile.

Important!As we go through, be sure to load the plugins into your Gulpfile/Gruntfile in the order you see in this tutorial; load order is important in PostCSS to keep everything running smoothly.

Add Imports for Partials

The very first place we’re going to start with putting together our custom preprocessor is imports. You have already seen PostCSS inlining of @importstylesheets in the previous tutorials For Minification and Optimizationand Preprocessing with PreCSS. The way imports will be used in this preprocessor is no different.

We just touched above on the fact that load order is important in PostCSS, and here we find the first example of this.We want to ensure all @importfiles are inlined as the very first step, so that we have all the code of our project in one place for the rest of our plugins to run against.

For example, we might store all our variables in a partialfile, and use @importto bringthat partial into our main stylesheet. If we didn’t run the plugin that inlines @importfiles first, our variables wouldn’t be imported and hence wouldn’tavailable for the rest of our processing to work with.

First, Change Gulpfile Source File to “style.css”

Because we’re going to start importing partials, we want to make a little tweak to our Gulpfile before we add our import functionality.

Note: if using Grunt, you won’t need to make any changes at this stage.

Right now we’re having any “.css” file found in the “src” folder compiled, but we don’t want to accidentally compile partial files. We’ll be importing everything into our “style.css” file so it’s the only one that needs to be compiled.

Find this line:

return gulp.src('./src/*.css')

...and change it to:

return gulp.src('./src/style.css') Import Plugin Used: postcss-import by Maxime Thirouin: https://github.com/postcss/postcss-import

This is the same plugin we used in the “For Minification and Optimization” tutorial, and that is also used in PreCSS, so you’ll be somewhat familiar with it at this point.

Install the plugin into your project, then in your “src” folder create a file named “_vars.css” and add some basic test code to it. Note we haven’t added variables functionality yet, so just some straight CSS, for example:

.test { background: black;}

Now import your new variables file into your main “src/style.css” file by adding this code at the first line:

@import "_vars";

Compile your code, then check your “dest/style.css” file and you should see it now contains the code from your “_vars.css” file.

Add Mixins Mixins Plugin Used: postcss-mixins byAndrey Sitnik: https://github.com/postcss/postcss-mixins

Note: this plugin mustbe executed before the postcss-nestedand postcss-simple-varsplugins, both of which we’ll be using.

Go ahead and install postcss-mixins, then add the following code to your “src/style.css” file:

@define-mixin icon $network, $color {.button.$(network) {background-image: url('img/$(network).png');background-color: $color;}}@mixin icon twitter, blue;

After compilation your “dest/style.css” should have the following compiled code added to it:

.button.twitter { background-image: url('img/twitter.png');background-color: blue;}

The postcss-mixinsplugin we’re using here is the same one asused in PreCSS. We went overhow to use itin the tutorial on PreCSS,so for full details on its syntax check out the “Mixins” section of the previous tutorial.

Other Mixin PluginOptions: postcss-sassy-mixins by Andy Jansson: https://github.com/andyjansson/postcss-sassy-mixins

If you’d prefer to use Sass syntax when creating mixins, check out Andy Jansson's postcss-sassy-mixinsplugin,which works in the same way as postcss-mixinsbut with the syntax @mixinto define a mixin, and @includeto use one.

Add “for” Loops “for” LoopsPlugin Used: postcss-for byAnton Yakushev: https://github.com/antyakushev/postcss-for

Note: the postcss-forplugin is another that mustbe executed before postcss-nestedand postcss-simple-vars.

Install the postcss-forplugin, then test it’s working as expected by adding this code to your “src/style.css” file:

@for $i from 1 to 3 { p:nth-of-type($i) {margin-left: calc( 100% / $i );}}

It should compile to give you:

p:nth-of-type(1) {margin-left: calc( 100% / 1 );}p:nth-of-type(2) {margin-left: calc( 100% / 2 );}p:nth-of-type(3) {margin-left: calc( 100% / 3 );}

Once again, the plugin we’re using to add @forloops is the same as is used in PreCSS, so for extra information on its syntax check out the “Loops” section in the previous tutorial.

Other “for” Loop PluginOptions: Fork of postcss-for byAnton Yakushev: https://github.com/xori/postcss-for

The postcss-forplugin has to be run before postcss-simple-vars, which means there’s no way tousevariables to set the range you want your @forloop to iterate through.

If this is a problem, you can instead use this forkof the postcss-for plugin that should instead be loaded afterthe postcss-simple-vars plugins.

Because it runs after variables are evaluated, you are free to use variables to set the range you want your @forloop to iterate through,likethis:

@from: 1;@count: 3;@for $i from @from to @count { p:nth-of-type($i) { margin-left: calc( 100% / $i );}} Add Variables

We’re going to be adding two kinds of variables to our preprocessor, both of which can be very handy.The first kind uses Sass-like syntax, and the second uses the syntax of CSS custom properties, otherwise known as CSS variables.

Variables Plugins Used: postcss-simple-vars byAndrey Sitnik: https://github.com/postcss/postcss-simple-vars postcss-css-variables byEric Eastwood: https://github.com/MadLittleMods/postcss-css-variables

Install these two plugins, then we’ll test each one at a time.

First, we’ll test the Sass-like syntax of postcss-simple-vars.Open up the “_vars.css” file you made earlier, delete its contents and add the following code:

$default_padding: 1rem;

Add the following to your “src/style.css” file and recompile:

.post { padding: $default_padding;}

It should compile to give you:

.post { padding: 1rem;}

Now we’ll test the CSS custom properties like syntax of postcss-css-variables. Add the following code toyour “src/style.css” file:

:root {--h1_font_size: 3rem;}h1 {font-size: var(--h1_font_size);}@media ( max-width: 75rem ){h1 {--h1_font_size: 4vw;}}

It should compile into:

h1 {font-size: 3rem;}@media ( max-width: 75rem ) {h1 {font-size: 4vw;}}

Notice that when using CSS variables, we only had to change the value of the --h1_font_sizevariable inside the media query and it automatically output the associated font-sizeproperty. This is particularly useful functionality.

Why Use Both Kinds ofVariables?

Before I go on I’ll just briefly mention again, that the approach taken in this tutorial is not the approach you haveto take. If you want to useone kind of variable and not the other, that’s completely fine.

From my perspective, the reason I like to use both kinds of variablesis I use them in twodifferent ways. I will typically use the CSS custom properties syntax in my main stylesheet, while I use Sass-like variables in my partial files.

This lets me set out CSS custom properties for the type of variables I might actually use in a live project if/when they become well supported across browsers. As you saw in the above example, they also have certain functionality that Sass-like variables do not.

Meanwhile, I can use Sass-like variables for things that don’t belong in a live stylesheet, especially those that exist purelyto be processed through through things like each loops,conditionals and other transformations.

Other Variables PluginOptions: postcss-advanced-variables by Jonathan Neal: https://github.com/jonathantneal/postcss-advanced-variables

As an alternative to using postcss-simple-vars you might like to consider using postcss-advanced-variables, the pluginused as part of the PreCSS pack.

This is also an excellent option, with the primary difference being it handles conditionals, loopsand variables allin the same plugin. For me, the reason I currently choose postcss-simple-vars is I prefer to have conditionals coming from a separate plugin; postcss-conditionals which we’ll cover shortly.

postcss-custom-properties byMaxime Thirouin: https://github.com/postcss/postcss-custom-properties

Instead of using postcss-css-variables, you might prefer postcss-custom-properties.

The essential difference betweenthe two is postcss-custom-propertiesconforms strictly to the W3C spec for custom propertiesso you can be confident that you are writing only correct future CSS.On the other handpostcss-css-variables offers extra functionality, but in doing so it does not claim to have complete parity with spec.

I personally choose postcss-css-variables because I am using it in the context of preprocessing where I write a lot of non-spec code anyway. As suchI’d rather have the added functionality over 100% spec compliance.

However, if you’re using variables in the context of writing future CSS, you might find postcss-custom-properties is a better fit for you.

Add “each”Loops “each” LoopPlugin Used: postcss-each byAlexander Madyankin: https://github.com/outpunk/postcss-each

Install the postcss-eachplugin then add this variable code to your “_vars.css” file:

$social: twitter, facebook, youtube;

This code defines a list, stored in the $socialvariable.

Now we’re going to create an @eachloop to iterate through the values stored in our $socialvariable.Add this code to your “src/style.css” file:

@each $icon in ($social){ .icon-$(icon) {background: url('img/$(icon).png');}}

Our @eachloop is now ready, but before we can compile it we need to make a little configuration change to the options of postcss-simple-vars.

You’ll notice that in the codeabove we’re using $iconto represent the current value we’re iterating through.Some difficulty can arise from thisbecause the postcss-simple-vars plugin looks for the $sign in order to identifyvariables.

This means it will see $icon,thinkit’s a variable, try to process it, then seeit doesn’t have a value. That will make it stop compiling and log an error to the console that it’s discovered an undefined variable.

To resolve this, we want to add the option silent: trueto our options for the plugin. This means that if it discovers an undefined variable it won’t stop compiling to log an error, it will just carry on. Hence it won’t be bothered by the presence $iconin our @eachloop and we’ll be able to compile successfully.

In the processors array of your Gulpfile or Gruntfile, set the option:

/* Gulpfile */simple_vars({silent: true})/* Gruntfile */require('postcss-simple-vars')({silent: true})

Now compile your CSS and you should get:

.icon-twitter {background: url('img/twitter.png');}.icon-facebook {background: url('img/facebook.png');}.icon-youtube {background: url('img/youtube.png');} Other “each” Loop PluginOptions: postcss-advanced-variables by Jonathan Neal: https://github.com/jonathantneal/postcss-advanced-variables

As mentioned earlier, postcss-advanced-variables is another excellent plugin option that handles variables, loops and conditionals all in one.

Add Conditionals Conditionals Plugin Used: postcss-conditionals by Andy Jansson: https://github.com/andyjansson/postcss-conditionals

I mentioned previously that this plugin is my preference for handling conditionals. This is because I have found it is able to handle more complex conditional checks. It includes support for @else ifsyntax, meaning you can test against more conditions in a single piece of code.

After installing the postcss-conditionals plugin, test it out by adding this code to your “src/style.css” file:

$column_count: 3;.column {@if $column_count == 3 {width: 33%;float: left;} @else if $column_count == 2 {width: 50%;float: left;}@else {width: 100%;}}

This code will check on the value we’ve set in the variable @column_countand will output different width and float values depending on what it finds. It works in the same way as the code we used in the previous preprocessingtutorial, but now that we have the ability to use @else iflines we’ve been able to increase the number of conditions we’re testing from two to three.

After recompiling this should give you:

.column { width: 33%;float: left}

Trychanging $column_countto 2or 1and compiling againto see how it changes the CSS output.

We can also use these types of conditionals well inside mixins, for which we added support earlier. For example, we can create a mixin to generate column layout code like so:

@define-mixin columns $count {@if $count == 3 {width: 33%;float: left;} @else if $count == 2 {width: 50%;float: left;}@else {width: 100%;}}.another_column {@mixin columns 2;}

This will give you the output:

.another_column { width: 50%;float: left;} Other ConditionalsOptions: postcss-advanced-variables by Jonathan Neal: https://github.com/jonathantneal/postcss-advanced-variables

As mentioned earlier, postcss-advanced-variables is another excellent plugin option that handles variables, loops and conditionals all in one.

Add Calc() for Math Calc() Plugin Used: postcss-calc byMaxime Thirouin: https://github.com/postcss/postcss-calc

In a previous tutorial we used postcss-calc, via cssnano, to help make instances of calc()use more efficient. In the context of preprocessing, however, it can be very useful wherever we might want to use math in our stylesheets.

Go ahead and install postcss-calc, then we’re going to test it out by making the column generation mixin we added above more efficient.

Right now we’re using conditionals to check if the mixin’s $countargument is set to either 1, 2or 3then outputting a corresponding pre-calculated width.Instead, we’ll use calc()to automatically output the right width for our column code, no matter what number is passed through the mixin.

Add to your “src/style.css” file:

@define-mixin columns_calc $count {width: calc( 100% / $count );@if $count > 1 {float: left;}}.column_calculated {@mixin columns_calc 2;}

Instead of hard codingthe percentage widths we’d need for certain numbers of columns, we’re now calculating it on the fly.

The postcss-calc plugin will convert width: calc( 100% / $count );into a static amount depending on the value passed when we call the mixin, in this case 2.

Recompile your code and you should see this output:

.column_calculated { width: 50%;float: left;}

Note: Wherever postcss-calc can resolve calc()toa static value it will output it into your code. If it can’t, it will change nothing,so you can still use calc()for values that need to be handled by the browser at runtime.

Add Nesting Nesting Plugin Used: postcss-nested byAndrey Sitnik: https://github.com/postcss/postcss-nested

For nesting we’re using the same plugin as is used in the PreCSS pack, so you can refer back to the previous tutorial for full information on syntax.

Install postcss-nestedthen test that everything is working properly by compiling this code:

.menu { width: 100%; a {text-decoration: none;}}

Your resulting CSS should be:

.menu {width: 100%;}.menu a {text-decoration: none;} Add Extends Extends Plugin Used: postcss-sass-extend by Jonathan Neal: https://github.com/jonathantneal/postcss-sass-extend

For extends we’ll be using the postcss-sass-extendplugin. Itwill give us different syntax to use than that we covered in our previous tutorialon working with PreCSS. Instead of extends being defined with @define-extend extend_name {...}they are defined with %extend_name {...}.

They are still used with the essentially thesame syntax of @extend %extend_name;.

Note that the postcss-sass-extendplugin does actually ship with PreCSS, however I assume it doesn’t load by default as when I attempted to use the required syntax it did not compile.

After installingpostcss-sass-extend into your project, test it out with the following code:

%rounded_button {border-radius: 0.5rem;padding: 1em;border-width: 0.0625rem;border-style: solid;}.blue_button {@extend %rounded_button;border-color: #2F74D1;background-color: #3B8EFF;}.red_button {@extend %rounded_button;border-color: #C41A1E;background-color: #FF2025;}

It should compile into:

.blue_button, .red_button { border-radius: 0.5rem; padding: 1em;border-width: 0.0625rem;border-style: solid;}.blue_button {border-color: #2F74D1;background-color: #3B8EFF;}.red_button {border-color: #C41A1E;background-color: #FF2025;} Other Extend PluginOptions: postcss-extend: https://github.com/travco/postcss-extend postcss-simple-extend: https://github.com/davidtheclark/postcss-simple-extend Extras

So far we’ve covered what could be considered the core features common to most preprocessors. However, there are still even more plugins available to offer extra features;some of these features arefound in otherpreprocessors, and some you have to go to PostCSS to find. We’ll go over these extra options briefly now.

Color Manipulation Plugins: postcss-color-function byMaxime Thirouin: https://github.com/postcss/postcss-color-function postcss-color-alpha byIvan Vlasenko: https://github.com/avanes/postcss-color-alpha postcss-color-scale byKristofer Joseph: https://github.com/kristoferjoseph/postcss-color-scale

The ability to tweak colors can beone of the most useful features found inpreprocessors.There are actually several color plugins for PostCSS, but these are threethat find themselves particularly at home in a preprocessing setup. They allow for various color transformations includinglightening, darkening, saturating, adding alpha values and more.

Property Definitions Plugin: postcss-define-property byDale Eidd: https://github.com/daleeidd/postcss-define-property

The functionality offered by this plugin could be compared tothe seamless mixins of Stylus, whereby, rather than using a syntax like @mixin, you define chunks of code in such a way that they can subsequently be used in code in the same way asa nativeproperty, e.g.

/* Define a property */size: $size { height: $size; width: $size;}/* Use it like a native property */.square { size: 50px;}

The plugin can also be used to redefine native properties to suit your needs.

Property Lookup Plugin: postcss-property-lookup by Simon Smith: https://github.com/simonsmith/postcss-property-lookup

Property lookup is a feature found in Stylus that can be very handy. It allows you to lookup the value of a property from within the same style. For example, you might set a right margin to match the left with: margin-left: 20px; margin-right: @margin-left;

Nested Properties Plugin: postcss-nested-props by Jed Mao: https://github.com/jedmao/postcss-nested-props

While the regular nesting we covered above unwraps selectors, the postcss-nested-props plugin unwraps nested properties, for example:

/* Origincal code */.element {border: {width: 1px;style: solid;color: #ccc;}}/* After processing */.element {border-width: 1px;border-style: solid;border-color: #ccc;} Matching Plugin: postcss-match byRyan Tsao: https://github.com/rtsao/postcss-match

Matching gives you another way to perform conditional checks, this time using Rust like pattern matching, something similar to switch statements in JavaScript or PHP. This can give you a more efficient way to check multiple conditions than writing many @if elsechecks.

CSS Sprite Generation Plugin: postcss-sprites byViktor Vasilev: https://github.com/2createStudio/postcss-sprites

CSS sprite generation, a popular feature in Compass, can also be done through the postcss-sprites plugin. The plugin will scan your CSS for images, combine those images into a sprite sheet, and update your code as required in order to display from the new sprite sheet correctly.

Lots More Choices

There is currently a really robust list of language extension plugins to choose from, more than we can cover here, so check out the full list at: https://github.com/postcss/postcss#language-extensions

Coming Soon: Alternative Syntaxes

For many people, the ability to write in terse, efficient syntax (typically sans semicolons and curly braces)is one of the big appeals of preprocessors like Stylus or Sass. The newly released version 5.0 of PostCSS now supports custom parsers which will enable new syntaxes to be supported. SugarSSis to be the terse syntax parser, and discussions are currently open on how this syntax will be structured.

You Can Always Add Your Own

PostCSS is still relatively new and you may find there is something you want to achieve with your custom preprocessor for which there’s currently no plugin. The beauty of this modular ecosystem is you have the option to solve that problem yourself by creating your own plugin. Anyone can do it, and the barrier to entry is far lower than were you to try and add your own functionality to Stylus, Sass or LESS. We’ll learn how in a later tutorial.

In the Next Tutorial

You don’t have to choose between PreCSS and rolling your own preprocessor if you want to use PostCSS. You can actually opt out of any PostCSS-based preprocessing entirely if you choose, instead using it side by side with your favorite preprocessor.

In the next tutorial we’ll learn how to use PostCSS in conjunction with Stylus, Sass or LESS. See you there!



分享给朋友:
您可能感兴趣的文章:
随机阅读: