AbsurdJS fundamentals

Category: home/JavaScript
Date: 2013-11-11

AbsurdJS became one of my favorite projects. I spent a good amount of time developing it and it's getting more and more interesting. I also received some positive feedback, so I think it is time to write a bit more about the module and explain how it actually works and what exactly is made for.

The concept

As many other front-end developers, I'm also a big fan of the CSS preprocessors. I started with LESS and after few projects I switched to SASS. They both have wonderful features, but they also have some disadvantages. At some point, I wanted to create my own CSS framework. After few days thinking and trying different approaches I realize that I can't really write the things like I wanted. And that's because neither LESS or SASS have the syntax, which I needed. If you are interested, before a couple of weeks I wrote an article about some of the issues which I met.

 

In such situations I normally dig for something which could solve my problems. However, this time I decided to reinvent the wheel and write my own CSS preprocessor. I really enjoy coding JavaScript, so I decided to work with the NodeJS. Normally my strategy for writing something big is to visualize all the processes. By knowing that I'm able to plan my work. Here is what I had:

Input (custom syntax)
  ↓
Parser
  ↓
Structured data
  ↓
Compiler (smart logic)
  ↓
Output (CSS)

There were two problems - the first one is that I had to invent my own language syntax, which means that I have to also write a custom parser, which understands that language. The second problem is that I have to write that smart logic, which will make my processor better then the others. Or at least will solve the problems, which I had.

 

I think a bit and decided that I'm not that smart to handle the first issue. Then I got one of those a-ha moments. I didn't have to invent a new language. I have one, which perfectly fits in my needs - JavaScript. In other words I decided to write my CSS with JavaScript.

Setup

As I mentioned above, AbsurdJS is mainly distributed as NodeJS module. So, once you run npm install -g absurd you will get the library available in your console. The simplies way to use it is to provide a source file. For example:

absurd -s ./code.js

code.js should contain the usual NodeJS module. What you should remember is that your function receives a reference to the AbsurdJS's API.

module.exports = function(api) {
    api.add({
        body: {
            fontSize: "10px",
            p: { fontSize: "inherit" }
        }
    });
}

The result of the above code is as follows:

body {
  font-size: 10px;
}
body p {
  font-size: inherit;
}

Along with a source file you may pass few other parameters. Like for example, saving the result to a file:

absurd -s ./code.js -o ./styles.css

The example below uses all the available options. I.e. compiling to a file, minify the output and run a watcher against the current directory.

absurd -s ./code.js -o ./styles.css -m true -w ./

All the things so far are presenting the CLI version of the module. However, you may need to integrate it directly into your application. If that's the case then you have to add AbsurdJS to your package.json file:

"dependencies": { 
    "absurd": "latest"
}

Later in your code:

var Absurd = require("absurd");
Absurd(function(api) {
    // use the Absurd's api here
}).compile(function(err, css) {
    // do something with the css
});

If you are using Grunt you will be happy to see that there is a module which plays well with AbsurdJS. It's called grunt-absurd. Its integration is pretty much the same as every other plugin.

grunt.loadNpmTasks('grunt-absurd');

Your configuration should look like that:

grunt.initConfig({
  absurd: {
    task: {
      src: 'path to your .js file',
      dest: 'path to your .css file'
    }
  },
});

After I published the library and make some noise few people suggested that the whole thing should be ported for browser usage. I'm not a big fen of this approach, because I believe that the preprocessors should be used only during the development process. In the production environment we normally use the result of their work. However, there are some cases where the library could be very handy. For example, if you work on a highly component separated application and you want to deliver every piece in just a single JavaScript file. You could use AbsurdJS to extract the CSS and add it to your document. Of course, not all the features are transferred to the browser. If something is missing, that's probably because it requires some native NodeJS modules.

Once I finished with the porting the file was less then 10Kb, which is kinda cool. You only need to include it in your page

<script src="absurd.min.js"></script>

and then use a code like this one:

var api = Absurd();
api.add({
    body: {
        marginTop: "20px",
        p: { color: "#000" }
    }
});
api.compile(function(err, css) {
    // use the compiled css
});

That's how the online compilator works. It is available here.

The power of JavaScript

Almost every article about CSS preprocessing starts with introduction to the variables. Actually that's one of the key features. If you write a lot of pure CSS, you probably end up with copy-pasting code again and again. The ability to define something in one place and use it in another is really powerful. And because AbsurdJS is using JavaScript you have not only variables available, but functions, objects, arrays and all those things used in the modern front-end development.

CSS is all about properties and their values. In JavaScript we still have the same thing.

var styles = {
    color: "#000",
    fontSize: "24px"    
}

In the context of AbsurdJS every property which has a string or number as value is convert to the usual CSS properties. If the value is actually another object the library transform the property to selector. For example the following object

var styles = {
    body: {
        fontSize: "20px",
        p: {
            fontSize: "30px"
        }
    }           
}

is converted to:

body {
  font-size: 20px;
}
body p {
  font-size: 30px;
}

Of course sometimes you don't have everything into one object and you need to mix them. In such cases you may pass an array.

var a = { fontSize: "20px" };
var b = { margin: 0 };
var styles = {
    body: [a, b]        
}

The result is:

body {
  font-size: 20px;
  margin: 0;
}

It's nice that you could use variables and objects to construct a stylesheet. LESS and SASS have something called mixins. That's a nice way to group styles definition in one place and avoid the annoying copy-pasting. As you may guess, that's really easy in JavaScript.

var radius = function(px) {
    return {
        WebkitBorderRadius: px,
        MozBorderRadius: px,
        MsBorderRadius: px,
        OBorderRadius: px,
        borderRadius: px
    }
}
var styles = {
    section: radius("6px")
}

Inheritance / extending

By definition inheritance is a way to establish relationships between classes or objects. In the context of CSS, inheritance is a way to reuse already predefined styles. That's necessary, because otherwise, you need to add same things to a lot of places. For example, let's say that we need several types of buttons. They all have different background-color, but their size and border-radius is the same. So, we need a base class and few child classes which extend it.

var extend = function(dest, source) {
    for(var key in source) {
        if(hasOwnProperty.call(source, key)) dest[key] = source[key];
    }
    return dest;
};
var button = function() {
    return {
        width: "140px",
        height: "80px",
        borderRadius: "6px",
        backgroundColor: "#aaa",
        color: "#000"
    }
}
var buttonCancel = extend(button(), { color: "#0000B9"});
var buttonOK = extend(button(), { color: "#00A390"});
var styles = {
    ".button": button(),
    ".cancel": buttonCancel,
    ".ok": buttonOK
}
api.add(styles);

The function extend is a helper method, which accepts two objects and merge their properties. The base class is the button function. The other buttonCancel and buttonOK objects use the styles from the base button and define their own font color. The resulted CSS is:

.button, .cancel, .ok {
  width: 140px;
  height: 80px;
  border-radius: 6px;
  background-color: #aaa;
}
.button {
  color: #000;
}
.cancel {
  color: #0000B9;
}
.ok {
  color: #00A390;
}

Notice, that AbsurdJS combines same classes into one definition and put different selectors only for the color property.

The example above illustrates how you could make simple inheritance with your styles. However, JavaScript has several ways to achieve the same thing. You may use the usual prototype class definition, the module pattern or some other technique. That's actually the biggest benefit of AbsurdJS - you could program your CSS. It's not only definition of styles, but it's like coding a real JavaScript application.

Customization

I believe that every library should accept modification. This should be an easy process, which doesn't require changes in the core of the module. Ideally, there should be an API methods for this. The first thing which I wanted to add was the ability to define custom CSS properties, which later to be saved as normal CSS styles.

api.plugin("absolute", function(api, values) {
    return {
        position: "absolute",
        top: values.y + "px",
        left: values.x + "px"
    };
});
var styles = {
    a: {
        color: "#000",
        absolute: { x: 20, y: 30 }
    }
}
api.add(styles);

I called these modifications plugins. In the example above, the plugin absolute is a shortcut to

position: absolute;
top: 30px;
left: 20px;

The plugins are very handy when they are used in the context of your current project, because you are able to create your own properties. I mean, the abstracting of CSS styles is a nice trick to get the things organized. For example:

border: 1px dotted #000;
color: #BADA55;
font-size: 14px;

Doesn't mean anything. When you read this, you only get information about the visual part of the things. However, if you use AbsurdJS you may write:

api.plugin("navbutton", function(api, color) {
    return {
        border: "1px dotted #000",
        color: color,
        fontSize: "14px"
    };
});
var styles = {
    a: {
        color: "#000",
        navbutton: "#BADA55"
    }
}

Now, everyone could understand that those styles are used for the buttons of your navigation.

AbsurdJS has two other mechanisms for modifications. The first one is hooks and it provides a way to execute your own JavaScript function for every API method. For example:

api.hook("add", function(rules) {
    console.log("hook - add method");
});
api.add({body: { color: "#000", margin: 0, padding: 0 }});
api.add({a: { textDecoration: "none" }});

If your handler returns true then only your hook will be executed. The original implementation of the method will be skipped.

 

The second way to patch the library is to pass your own processor. If you are not happy with the default one you my write your own.

var processor = function(rules, callback, options) {
    callback(null, rules);
};
api.add({
    body: {
        p: { fontSize: "20px" },
        section: { background: "#BADA55" }
    }
});
api.compile(function(err, json) {
    // json object here contains the raw
    // information stored in Absurd.
    // Check your console to see the result.
    console.log(json);
}, { processor: processor });

This actually means that you may write another type of preprocessor. I.e. you add JavaScript objects, AbsurdJS stores them and you are free to process them as you want.

Final words

AbsurdJS is probably not the best CSS preprocessor, but it may change your workflow completely. It brings flexibility and it is very handy if you need to write something big. Because it uses JavaScript you don't have to learn a new language. You are free to use it even now.

 

The official documentation of the library and online compiler is available here http://krasimir.github.io/absurd/. The project is placed in GitHub (https://github.com/krasimir/absurd), so feel free to fork it and make modifications.


blog comments powered by Disqus