As a front-end developer, I'm writing a lot of CSS and using pure CSS is not the most efficient way nowadays. CSS preprocessors are something which have helped me a lot. My first impression was that I finally found the perfect tool. These have a bunch of features, great support, free resources and so on. This is all true and it still applies, but after several projects, I realized that the world is not so perfect. There are two main CSS preprocessors - LESS and SASS. There are some others, but I have experience with only these two. In the first part of this article I'll share with you what I don't like about preprocessors and then in the second part I'll show you how I managed to solve most of the problems that I had.
The Problems
Setup
No matter which CSS preprocessor is involved, there is always setup required, like, you can't just start typing .less
or .sass
files and expect to get the .css
file. LESS requires NodeJS and SASS Ruby. At the moment, I'm working mostly on HTML/CSS/JavaScript/NodeJS applications. So, LESS seems like a better option, because I don't need to install additional software. You know, adding one more thing to your ecosystem means more time for maintenance. Also, not only do you need the required tool, but all of your colleagues should now integrate the new instrument as well.
Firstly, I chose LESS because I already had NodeJS installed. It played well with Grunt and I successfully finished two projects with that setup. After that, I started reading about SASS. I was interested in OOCSS, Atomic design and I wanted to build a solid CSS architecture. Very soon I switched to SASS, because it gave me better possibilities. Of course I (and my colleagues too) had to install Ruby.
Output
A lot of developers don't check the produced CSS. I mean, you may have really good looking SASS files, but what's used in the end is the compiled .css
file. If it is not optimized and its file size is high, then you have a problem. There are few things which I don't like in both preprocessors.
Let's say that we have the following code:
// LESS or SASS p { font-size: 20px; } p { padding: 20px; }
Don't you think that this should be compiled to:
p { font-size: 20px; padding: 20px; }
Neither LESS nor SASS works like that. They just leave your styles as you type them. This could lead to code duplication. What if I have complex architecture with several layers and every layer adds something to the paragraph. There will be several definitions, which are not exactly needed. You may even have the following situation:
p { font-size: 20px; } p { font-size: 30px; }
The correct code at the end should be only the following:
p { font-size: 30px; }
I now know that the browser will take care and will find out the right font size. But, isn't it better to save those operations. I'm not sure that this will affect the performance of your page, but it affects the readability for sure.
Combining selectors which share the same styles is a good thing. As far as I know, LESS doesn't do this. Let's say that we have a mixin and we want to apply it to two classes.
.reset() { padding: 0; margin: 0; } .header { .reset(); } .footer { .reset(); }
And the result is:
.header { padding: 0; margin: 0; } .footer { padding: 0; margin: 0; }
So, these two classes have the same styles and they could be combined into one definition.
.header, .footer { padding: 0; margin: 0; }
I was wondering if this is actual performance optimization, and I didn't find an accurate answer, but it looks like a good thing. SASS has something called place holders
. It's used exactly for such situations. For example:
%reset { padding: 0; margin: 0; } .header { @extend %reset; } .footer { @extend %reset; }
The code above produces exactly what I wanted. The problem is that if I use too many place holders I may end up with a lot of style definitions, because the preprocessor thinks that I have something to combine.
%reset { padding: 0; margin: 0; } %bordered { border: solid 1px #000; } %box { display: block; padding: 10px; } .header { @extend %reset; @extend %bordered; @extend %box; }
There are three place holders. The .header
class extends them all and the final compiled CSS looks like this:
.header { padding: 0; margin: 0; } .header { border: solid 1px #000; } .header { display: block; padding: 10px; }
It looks wrong, doesn't it? There should be only one style definition and only one padding
property.
.header { padding: 10px; margin: 0; border: solid 1px #000; display: block; }
Of course, there are tools which may solve this, having the compiled CSS. But as I said, I prefer to use as less libraries as possible.
Syntax Limitation
While I was working on OrganicCSS, I met a lot of limitations. In general, I wanted to write CSS as I write JavaScript. I mean, I had some ideas about complex architecture, but I wasn't able to achieve them, because the language which I was working with was kinda primitive. For example, let's say that I need a mixin which styles my elements. I want to pass a theme and border type. Here is how this should look in LESS:
.theme-dark() { color: #FFF; background: #000; } .theme-light() { color: #000; background: #FFF; } .component(@theme, @border) { border: "@{border} 1px #F00"; .theme-@{theme}(); } .header { .component("dark", "dotted"); }
Of course I'll have a lot of themes and they should also be mixins. So, the variable interpolation works for the border property, but not for the mixin names. That's a simple one, but it is currently not possible, or at least I don't know if it is fixed. If you try to compile the above code you will get Syntax Error on line 11
.
SASS is one step further. The interpolation works with placeholders, which makes things a little bit better. The same idea looks like this:
@mixin theme-dark() { color: #FFF; background: #000; } @mixin theme-light() { color: #000; background: #FFF; } %border-dotted { border: dotted 1px #000; } @mixin component($theme, $border) { @extend %border-#{$border}; @include theme-#{$theme}; } .header { @include component("dark", "dotted"); }
So, the border styling works, but the theme produces:
Sass Error: Invalid CSS after " @include theme-": expected "}", was "#{$theme};"
That's because the interpolation in the names of the mixins and extends is not allowed. There is a long discussion about that and will probably be fixed soon.
Both LESS and SASS are great if you want to improve your writing speed, but they are far from perfect for building modular and flexible CSS. Mainly, they are missing things like encapsulation, polymorphism and abstraction. Or at least, they are not in the form which I needed.
A New Approach
I fought several days with those limitation. I invested a good amount of time reading documentation. In the end, I just gave up and started looking for other options. What I had wasn't flexible enough and I started thinking about writing my own preprocessor. Of course, that's a really complex task and there are a lot of things to think about, such as:
- the input - normally preprocessors take code which looks like CSS. I guess the idea is to complete the language, such as add in missing, yet necessary features. It is also easy to port pure CSS and the developers could start using it immediately, because in practice, it is almost the same language. However, from my point of view, this approach brings few difficulties, because I had to parse and analyze everything.
- the syntax - even if I write the parsing part, I had to invent my own syntax which is kind of a complex job.
- competitors - there are already two really popular preprocessors. They have good support and an active community. You know, most of the coolest things in our sphere are so useful, because of the contributers. If I write my own CSS preprocessor and I don't get enough feedback and support from the people, I may be the only one which is actually using it.
So, I thought about it a bit and found a solution. There is no need to invent a new language with a new syntax. It's already there. I could use pure JavaScript. There is already a big community and a lot of people may start using my library immediately. Instead of reading external files, parsing and compiling them, I decided to use the NodeJS ecosystem. And of course, the most important thing - I completely removed the CSS part. Writing everything in JavaScript made my web application a lot cleaner, because I didn't have to deal with the input format and all those processes which produces the actual CSS.
(The name of the library is AbsurdJS. You may find this name funny and it is indeed. When I share my idea with some friends they all said, writing your CSS in JavaScript - absurd. So, that was the perfect title.)
AbsurdJS
Installation
To use AbsurdJS you need NodeJS installed. If you still don't have this little gem on your system go to nodejs.org and click the Install
button. Once everything finishes you could open a new console and type:
npm install -g absurd
This will setup AbsurdJS globally. This means that wherever you are, you may run the absurd
command.
Writing Your CSS
In the JavaScript world, the closest thing to CSS is JSON format. So, that's what I decided to use. Let's take a simple example:
.content { padding: 0; margin: 0; font-size: 20px; } .content p { line-height: 30px; }
This is pure CSS. Here is how it looks like in LESS and SASS:
.content { padding: 0; margin: 0; font-size: 20px; p { line-height: 30px; } }
In the context of AbsurdJS the snippet should be written like this:
module.exports = function(api) { api.add({ '.content': { padding: 0, margin: 0, 'font-size': '20px', p: { 'line-height': '30px' } } }); }
You may save this to a file called styles.js
and run:
absurd -s .\styles.js
It will compile the JavasSript to the same CSS. The idea is simple. You write a NodeJS package, which exports a function. The function is called with only one parameter - the AbsurdJS API. It has several methods and I'll go through them later, but the most common one is add
. It accepts valid JSON. Every object defines a selector. Every property of that object could be a CSS property and its value or another object.
Importing
Placing different parts of your CSS in different files is really important. This approach improves the readability of your styles. AbsurdJS has an import
method, which acts as the @import
directive in the CSS preprocessors.
var cwd = __dirname; module.exports = function(api) { api.import(cwd + '/config/main.js'); api.import(cwd + '/config/theme-a.js'); api.import([ cwd + '/layout/grid.js', cwd + '/forms/login-form.js', cwd + '/forms/feedback-form.js' ]); }
What you have to do is write a main.js
file which imports the rest of the styles. You should know that there is overwriting. What I mean is that if you define a style for the body
tag inside /config/main.js
and later in /config/theme-a.js
use the same property, the final value will be the one used in the last imported file. For example:
module.exports = function(api) { api.add({ body: { margin: '20px' } }); api.add({ body: { margin: '30px' } }); }
Is compiled to
body { margin: 30px; }
Notice that there is only one selector. While, if you do the same thing in LESS or SASS you will get
body { margin: 20px; } body { margin: 30px; }
Variables and Mixins
One of the most valuable features in preprocessors are their variables. They give you the ability to configure your CSS, such as define a setting somewhere in the beginning of the stylesheet and use it later on. In JavaScript, variables are something normal. However, because you have modules placed in different files you need something that acts as a bridge between them. You may want to define your main brand color in one file, but later use it in another. AbsurdJS offers an API method for that, called storage
. If you execute the function with two parameters, you create a pair: key-value
. If you pass only a key, you actually get the stored value.
// config.js module.exports = function(api) { api.storage("brandColor", "#00F"); } // header.js module.exports = function(api) { api.add({ header: { color: api.storage("brandColor") } }) }
Every selector may accept not only an object, but also an array. So this is also valid:
module.exports = function(api) { api.add({ header: [ { color: '#FF0' }, { 'font-size': '20px' } ] }) }
This makes sending multiple objects to specific selectors possible. It plays very well with the idea of mixins. By definition, the mixin is a small piece of code which could be used multiple times. That's the second feature of LESS and SASS, which makes them attractive for developers. In AbsurdJS the mixins are actually normal JavaScript functions. The ability to put things inside storage gives you the power to share mixins between the files. For example:
// A.js module.exports = function(api) { api.storage("button", function(color, thickness) { return { color: color, display: "inline-block", padding: "10px 20px", border: "solid " + thickness + "px " + color, 'font-size': "10px" } }); } // B.js module.exports = function(api) { api.add({ '.header-button': [ api.storage("button")("#AAA", 10), { color: '#F00', 'font-size': '13px' } ] }); }
The result is:
.header-button { color: #F00; display: inline-block; padding: 10px 20px; border: solid 10px #AAA; font-size: 13px; }
Notice, that there is only one selector defined and the font-size
property has the value from the second object in the array (the mixin defines some basic styles, but later they are changed).
Plugins
Ok, mixins are cool, but I always wanted to define my own CSS properties. I mean using properties that don't normally exist, but encapsulate valid CSS styles. For example:
.header { text: medium; }
Let's say that we have three types of text: small
, medium
and big
. Each of them has a different font-size
and different line-height
. It's obvious that I can achieve the same thing with mixins, but AbsurdJS offers something better - plugins. The creation of the plugin is again via the API:
api.plugin("text", function(api, type) { switch(type) { case "small": return { 'font-size': '12px', 'line-height': '16px' } break; case "medium": return { 'font-size': '20px', 'line-height': '22px' } break; case "big": return { 'font-size': '30px', 'line-height': '32px' } break; } });
This allows you to apply text: medium
to your selectors. The above styling is compiled to:
.header { font-size: 20px; line-height: 22px; }
Media Queries
Of course the library supports media queries. I also copied the idea of the bubbling
feature (you are able to define breakpoints directly inside the elements and AbsurdJS will take care for the rest).
api.add({ '.footer': { 'font-size': '14px', '@media all and (min-width: 320px) and (max-width: 550px)': { 'font-size': '24px' } }, '.content': { '@media all and (min-width: 320px) and (max-width: 550px)': { margin: '24px' } } })
The result is:
.footer { font-size: 14px; } @media all and (min-width: 320px) and (max-width: 550px) { .footer { font-size: 24px; } .content { margin: 24px; } }
Keep in mind that if you have the same media query used multiple times, the compiled file will contain only one definition. This actually saves a lot of bytes. Unfortunately LESS and SASS doesn't do this.
Pseudo Classes
For these, you just need to pass in valid JSON. The following example demonstrates how to use pseudo CSS classes:
module.exports = function(api) { api.add({ a: { 'text-decoration': 'none', ':hover': { 'text-decoration': 'underline' } } }); }
And it is compiled to:
a { text-decoration: none; } a:hover { text-decoration: underline; }
Integration
AbsurdJS works as a command line tool, but it could be used inside a NodeJS application as well. For example:
var Absurd = require("absurd"), absurd = Absurd(), api = absurd.api, output = "./css/styles.css"; api.add({ ... }).import("..."); absurd.compileFile(output, function(err, css) { // do something with the css });
Or if you have a file which acts as an entry point:
var Absurd = require("absurd"); Absurd("./css/styles.js").compileFile("./css/styles.css", function(err, css) { // do something with the css });
The library also supports integration with Grunt. You can read more about that on the following Github page.
Command Line Interface Options
There are three parameters available:
- [-s] - main source file
- [-o] - output file
- [-w] - directory to watch
For example, the following line will start a watcher for a ./css
directory, will grab ./css/main.js
as an entry point and will output the result to ./styles.css
:
absurd -s ./css/main.js -o ./styles.css -w ./css
Conclusion
Don't get me wrong. The available CSS preprocessors are awesome, and I'm still using them. However, they came with their own set of problems. I managed to solve them by writing AbsurdJS. The truth is that I just replaced one tool with another. The usage of this library eliminates the usual writing of CSS and makes things really flexible, because everything is JavaScript. It could be used as a command line tool or it could be integrated directly into the application's code. If you are interested in AbsurdJS, feel free to check out the full documentation at github.com/krasimir/absurd or fork the repo.
Comments