Modular CSS preprocessing with rework
Several months ago I started a project named Rework, a very fast, simple, flexible, and modular CSS preprocessor. The biggest and most obvious question I get is how this tool compares to something like Stylus, LESS, or Sass, and why would you want to use it.
The simple answer is that Rework caters to a different audience, and provide you the flexibility to craft the preprocessor you want, not the choices the author(s) have force on you.
Our goal with Rework contrasts the others, as it does not provide a higher level language within the CSS documents, Rework’s goal is to make those constructs unnecessary, being the most transparent CSS pre-processor out there.
Speed
Rework was designed to be simple and fast out of the box, without implementing complex caching models allowing everyone to hack on Rework itself or associate plugins easily.
Powering rework is a css parser written in JavaScript that may be used with node.js or in the browser. On the other end there is the css compiler which accepts the AST from css-parse, outputting CSS.
So where does Rework come in? Rework is the plugin and transformation engine that sits in-between these two tools. The AST is manipulated by Rework and its plugins (or user-defined plugins), which is then passed to the compiler.
Because these transformations are performed in JavaScript and not in a specialized preprocessor language they are simple to implement and efficient.
Let’s check out what it can do!
Modularity & Flexibility
Before diving into the plugins bundled with Rework, let’s take a look at how they can be applied. Rework’s API is very simple, you can specify vendor prefixes with .vendors(), which all plugins have access to, or you may pass specific vendor prefixes to most plugins themselves. Plugins are passed to a .use() method, and then finally the CSS output is returned with .toString(). In practice this looks something like:
var rework = require('rework');
var css = rework('string of css here')
.vendors(['-webkit-', '-moz-'])
.use(rework.prefixValue('linear-gradient'))
.use(rework.prefixValue('radial-gradient'))
.use(rework.prefixValue('transform'))
.use(rework.vars())
.use(rework.colors())
.toString()
Out of the box rework provides plugins for the following, which may be mixed and matched as you wish:
- media macros — define your own @media queries
- ease — several additional easing functions
- at2x — serve high resolution images
- prefix — add vendor prefixes to properties
- prefixValue — add vendor prefixes to values
- prefixSelectors — add prefixes to selectors
- opacity — add IE opacity support
- url — rewrite
url()s with a callback function
- vars — add css variable support
- keyframes — add @keyframe vendor prefixing
- colors — add colour helpers like
rgba(#fc0, .5)
- references — add property references support
height: @width etc
- mixin — add custom property logic with mixing
- extend — add
extend: selector support
Let’s look at some examples!
Vendor prefixes
The prefix, prefixValue, and keyframes plugins make vendor prefixing extremely simple. Here the prefix plugin is applied to only two properties for illustration:
var rework = require('..')
var read = require('fs').readFileSync
var css = rework(read('examples/prefix.css', 'utf8'))
.vendors(['-webkit-', '-moz-'])
.use(rework.prefix('border-radius'))
.use(rework.prefix('box-shadow'))
.toString()
console.log(css)
With the prefix.css file containing the following CSS:
.button {
border-radius: 5px;
box-shadow: inset 0 0 1px white;
}
Rework yields the following prefixed output:
.button {
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
-webkit-box-shadow: inset 0 0 1px white;
-moz-box-shadow: inset 0 0 1px white;
box-shadow: inset 0 0 1px white
}
Extending
The extend plugin adds support for a special property named extend, which propagates the selector back up the AST so that the styles are applied without duplication. For example here’s a contrived green join button implementation:
button {
padding: 5px 10px;
border: 1px solid #eee;
border-bottom-color: #ddd;
}
.green {
background: green;
padding: 10px 15px
}
a.join {
extend: button;
extend: .green;
padding: 20px;
}
With this plugin enabled both of the extend properties are gone, and you’re left with the CSS you might expect:
button,
a.join {
padding: 5px 10px;
border: 1px solid #eee;
border-bottom-color: #ddd
}
.green,
a.join {
background: green;
padding: 10px 15px
}
a.join {
padding: 20px
}
Better rgba()
Ever had a color hex and then wanted to alter the alpha channel? Then you probably know how annoying this is with CSS. The colors plugin allows you to pass hex values to rgba(), for example rgba(#c00, .5) which is then compiled to rgba(204,0,0,0.5).
Writing your own plugins
Writing plugins for Rework is simple, suppose for example you want the ability to create your own properties, here expanding single-line position properties:
#logo {
absolute: top left;
}
#logo {
relative: top 5px left;
}
#logo {
fixed: top 5px left 10px;
}
Into the valid CSS shown below:
#logo {
position: absolute;
top: 0;
left: 0
}
#logo {
position: relative;
top: 5px;
left: 0
}
#logo {
position: fixed;
top: 5px;
left: 10px
}
The AST css-parse provides you is made up of simple objects and arrays, making it extremely easy to manipulate without extra knowledge of how the nodes are constructed or strange internal APIs. In the following plugin function we walk the declarations, check if the property is one of the positions, parse the value by splitting on whitespace and then inject the new css properties.
function positions() {
var positions = ['absolute', 'relative', 'fixed'];
return function(style){
rework.visit.declarations(style, function(declarations){
declarations.forEach(function(decl, i){
if (!~positions.indexOf(decl.property)) return;
var args = decl.value.split(/\s+/);
var arg, n;
// remove original
declarations.splice(i, 1);
// position prop
declarations.push({
property: 'position',
value: decl.property
});
// position
while (args.length) {
arg = args.shift();
n = parseFloat(args[0]) ? args.shift() : 0;
declarations.push({
property: arg,
value: n
});
}
});
});
}
}
To use this plugin you would simply pass it to .use():
var css = rework('#logo { absolute: top left; }')
.use(positions())
.toString()
Significant whitespace
Since CSS has a relatively simple grammar significant whitespace can help improve your productivity without turning the whole thing into an unreadable mess, this is especially true since Rework does not provide the higher level language constructs, which can get lost in the indentation. This is where css-whitespace comes in! It lets you write things like:
ul
li
background: #eee
&:hover
background: white
&:active
background: #ddd
Which yields:
ul li:hover {
background: white;
}
ul li:active {
background: #ddd;
}
ul li {
background: #eee;
}
Future
In the near future we’ll be providing a more intuitive way to apply all or large chunks of the functionality Rework provides without manually adding all of the plugins, as well as improving the rework(1) executable.
Mocha 1.0
The Mocha JavaScript test framework has hit 1.0 with a bunch of great contributions from the community, here’s the change log:
- Added js API. Closes #265
- Added: initial run of tests with
--watch. Closes #345
- Added: mark
location as a global on the CS. Closes #311
- Added
markdown reporter (github flavour)
- Added: scrolling menu to coverage.html. Closes #335
- Added source line to html report for Safari [Tyson Tate]
- Added “min” reporter, useful for
--watch [Jakub Nešetřil]
- Added support for arbitrary compilers via . Closes #338 [Ian Young]
- Added Teamcity export to lib/reporters/index [Michael Riley]
- Fixed chopping of first char in error reporting. Closes #334 [reported by topfunky]
- Fixed terrible FF / Opera stack traces
Compiler support
coffee-script out of the box was removed, now you can used the --compilers <ext>:<module>,... flag to map a compiler to the given extension name. For example mocha --compilers coffee:coffee-script. There are simply too many foo -> JavaScript transpilers to directly support, this pushes that back on the author.
Min reporter
First up is the min reporter by Jakub Nešetřil, this tiny reporter works great with --watch, outputting the summary only, though still reporting verbose errors on failure. 
Markdown reporter
I added a markdown reporter which can be used to display your tests as documentation in a Github wiki page, or simply a markdown file in your repository that you can link to. For example here are the Connect markdown test docs.
I’m not super happy with how much padding Github adds, so the TOC looks pretty messy, but all of these document-style reporting mechanisms make you think twice about how clean and organized your tests are.
JavaScript API
A new JS API was added, which mocha(1) now utilizes. This higher-level JS API will make it easier for those who want to script the testing process with Mocha. I have yet to document this API but it looks like this:
var Mocha = require('mocha');
var mocha = new Mocha;
mocha.reporter('spec').ui('bdd');
mocha.addFile('test/suite.js');
mocha.addFile('test/runner.js');
mocha.addFile('test/runnable.js');
var runner = mocha.run(function(){
console.log('finished');
});
runner.on('pass', function(test){
console.log('... %s passed', test.title);
});
runner.on('fail', function(test){
console.log('... %s failed', test.title);
});
Styling Canvas Drawings With CSS
Today I implemented an idea to style canvas elements with CSS, I’ve never personally seen this done, and it’s not always practical, but in some cases it would be very handy. For example a text selection api may be comprised of several “elements”, the text, the caret, the selection range rect etc.
Let’s say by default, our drawing will look the image below:

great, looks fine, but what if designers, or even developers for organizational purposes, could tweak this with CSS. Well you can! Suppose we want to support the following css:
#editor .text {
font: 20px helvetica, arial, sans-serif;
}
#editor .text .selection {
background: #DFF3FC;
}
First, the API to access such data will look very similar to how we wrote our css, however we provide the css property as well:
style('#editor .text .selection', 'font-family')
Now for the implementation details, keeping in mind this is not targeting cross-browser support, it’s simply a quick naive implementation, and I should note this does not cover the case of style changes, only initial styles.
So, firstly we check our cache, to prevent several expensive lookups, here style.cache is a property of our style function, if it’s available, we simply return the result.
function style(selector, prop) {
var cache = style.cache = style.cache || {}
, cid = selector + ':' + prop;
if (cache[cid]) return cache[cid];
...
Next up we will naively split the selector into an array so that we can work with it.
...
var parts = selector.split(/ +/)
, len = parts.length
, parent = root = document.createElement('div')
, child
, part;
...
We loop through the “parts”, creating one div per “part”. This will allow us to see what the computed style is for an element, that otherwise, does not exist! because it’s a canvas. The naive implementation I have here, supports ids and classes only, so we check the first character, and assign the id and class respectively.
...
for (var i = 0; i < len; ++i) {
part = parts[i];
child = document.createElement('div');
parent.appendChild(child);
parent = child;
if ('#' == part[0]) {
child.setAttribute('id', part.substr(1));
} else if ('.' == part[0]) {
child.setAttribute('class', part.substr(1));
}
}
...
Finally we append the root element to the document.body so that the styles are applied, get the computed style for the given prop, remove the element, and cache/return our value.
...
document.body.appendChild(root);
var ret = getComputedStyle(child)[prop];
document.body.removeChild(root);
return cache[cid] = ret;
}
Example Usage
Now let’s try it out, first, the css, providing a light yellow background:
#chalk .text .selection {
background: #FAFFDF;
}
In our JavaScript text selection logic, we should provide a JavaScript API for altering styling as well, however for this example, let’s add it directly:
ctx.fillStyle = style('#editor .text .selection', 'background-color');
ctx.fill(...)
Once applied, we get the following result:

That’s it! you can apply this trick to assign font sizes and families, foreground and background coloring, border colors, anything you can come up with! if anyone comes up with a robust implementation I would love to see it!
Full Source
Here’s the full implementation:
function style(selector, prop) {
var cache = style.cache = style.cache || {}
, cid = selector + ':' + prop;
if (cache[cid]) return cache[cid];
var parts = selector.split(/ +/)
, len = parts.length
, parent = root = document.createElement('div')
, child
, part;
for (var i = 0; i < len; ++i) {
part = parts[i];
child = document.createElement('div');
parent.appendChild(child);
parent = child;
if ('#' == part[0]) {
child.setAttribute('id', part.substr(1));
} else if ('.' == part[0]) {
child.setAttribute('class', part.substr(1));
}
}
document.body.appendChild(root);
var ret = getComputedStyle(child)[prop];
document.body.removeChild(root);
return cache[cid] = ret;
}
Redis Implemented With Node
Nedis is a (partial) redis implementation written with node. Primarily for fun, however as our team grows larger, and as we add more non-technical team members over at LearnBoost I figured it would be nice help prevent the need for compiling development dependencies.
Nedis is an old side project I had going, and is no where near complete, but it does work, so I figured I would open-source it. Currently Nedis implements the unified Redis protocol which is an brilliantly simple binary-safe protocol that is human and machine friendly.
Using Existing Tools
Currently we use Redis for sessions in our app, so having a drop-in replacement is a great way to get session support for your app without booting up redis-server. For example the nodejs module connect-redis can utilize Matt Ranney’s fantastic redis client without change.
Another neat side-effect is that you can use existing redis tools such as redis-cli to interact with Nedis. First let’s start Nedis with nedis-server:
$ nedis-server
Now we can play with the cli, interacting with node
$ redis-cli
redis> hmset users:tj email tj@vision-media.ca age 23
OK
redis> hgetall users:tj
1) "email"
2) "tj@vision-media.ca"
3) "age"
4) "23"
redis> keys users:*
1) "users:tj"
Note that nedis-server basically consists of no more than the line of js below, so it’s easy to boot from within your process if desired.
nedis.createServer(options).listen(port);
Supported Commands
Below is a list of the commands currently supported by Nedis
- PING
- ECHO
- QUIT
- SELECT
- HLEN
- HVALS
- HKEYS
- HSET
- HMSET
- HGET
- HGETALL
- HEXISTS
- TYPE
- EXISTS
- RANDOMKEY
- DEL
- RENAME
- KEYS
- FLUSHDB
- FLUSHALL
- DBSIZE
- INFO
- BGREWRITEAOF
- GET
- GETSET
- GET
- SETNX
- INCR
- INCRBY
- DECR
- DECRBY
- STRLEN
- APPEND
- SETRANGE
- GETRANGE
- MGET
- MSET
MSETNX
I have yet to do any kind of profiling, heavy optimization, or stress testing. If nothing more hopefully Nedis will help you guys explore Redis, or how you can prototype basic databases with node. Head over to the GitHub repo for installation instructions etc.