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.3.0

A relatively minor release this time but we’ve still got some cool new features!

Scrolling HTML reporter

The window will now scroll to follow along with larger test suites, and the statistics are fixed to the upper right-hand corner, a small but nice touch.

Custom reporters

Mocha has quite a few bundled reporters now, so future reporters that are not deemed important enough to provide the convenience of being in core should be published to npm. Some less important reporters may be removed from core and published to npm in the future.

This new feature also allows you to use localized private reporters. The --reporter NAME flag now behaves like require(), so for example you could do --reporter ./myreporter, or a third-party npm module such as --reporter lcov-reporter.

For details on creating and finding third-party reporters visit the wiki.

Grep inversion

The awesome --grep feature can now be inverted with the aptly named --invert flag. For example suppose you had tests tagged as @slow, and you wanted to run only slow tests you might use --grep @slow, however if you wanted to run only fast tests --grep @slow --invert would do the trick.

Along with this change --grep PATTERN is now escaped, meaning chars that used to behave as regexp special chars are now escaped.

Changelog

Here’s the full changelog:

1.3.0 / 2012-07-05

  • add window scrolling to HTML reporter
  • add v8 --trace-* option support
  • add support for custom reports via --reporter MODULE
  • add --invert switch to invert --grep matches
  • fix export of Nyan reporter. Closes #495
  • fix escaping of HTML suite titles. Closes #486
  • fix done() called multiple times with an error test
  • change --grep - regexp escape the input

Mocha 1.2.0 - now with more nyan!

Nyan

@atsuya added an awesome new nyan reporter:

Client-side test initialization

You can now initialize a client-side test setup with the mocha init <path> command. This will copy over mocha.css, mocha.js and generate a default tests.html file for you.

Working browser example

A lot of people were asking for a working browser example to reference, so I added one to the bottom of mocha’s site, you can find it here. This example test suite uses chai for assertions.

Wildcard global leaks

Global leak wildcard matching was added to flag globals such as “callback123” and “callback456” with “callback*” created by libraries like jQuery as acceptable since there’s little you can do to avoid this.

mocha 1.1.0

The Mocha javascript test framework version 1.1.0 is out. If you want a quick glance here’s the changelog:

Changelog

  • Added: check each mocha(1) arg for directories to walk
  • Added --recursive [tricknotes]
  • Added context for BDD [hokaccha]
  • Added styling for new clickable titles
  • Added clickable suite titles to HTML reporter
  • Added warning when strings are thrown as errors
  • Changed: green arrows again in HTML reporter styling
  • Changed ul/li elements instead of divs for better copy-and-pasting [joliss]
  • Fixed issue #325 - add better grep support to js api
  • Fixed: save timer references to avoid Sinon interfering.

Directory arguments

Mocha will now iterate the arguments passed and load files in any given directories (non-recursively). For example you may now use mocha spec if you have tests in ./spec instead of mocha spec/* - which is especially helpful for the unfortunate few stuck on windows.

Recursive

The new --recursive flag compliments the previous feature, but walks the directories recursively.

Filter with clickable titles

Suite titles are now clickable, auto-grepping. For example if you have the following suite:

full suite

You could use ?grep=SOMESTRING, or simply click the title to re-execute tests within that suite as shown here:

durations only

That’s it for now :)

mad(1) node.js pages

The node.js markdown documentation is now viewable in the terminal via mad(1):

node man page

Just use mad list to see which pages are available:

list of node man pages

If you already have mad(1) installed simply run mad --update to grab these new pages, and view with mad node.http etc.

Express 3.0.0 alpha1

I just released the first alpha, but beware! the documentation has not been updated. I wanted to defer a release until I had time to re-write the site, but meanwhile people have been using the master branch anyway, so we might as well make it official (and available via npm).

The alpha is by no means complete, some features still need adjusting, though mainly internal refactoring. The wiki contains both the 2.x to 3.x migration guide, and documentation for most of the new features added. It’s worth noting you’ll also gain new features from Connect 2.x as well, which Express 3.x depends on.

There are quite a few new http utilities, better reverse proxy support, and refinements to the view system. Express 3.x is even smaller than 2.x totalling 975 SLOC according to sloc(1), where Express 2.x was around 1300. Most of the effort goes into actually removing and refining code to increase quality, and lots of test coverage (4286 SLOC).

I’ve also started a Express 4.x Roadmap with some details on where things may go in the future.

Contributing

If you’d like to contribute, one of the best ways right now would be to help upgrade the ./examples ! The Express repo has many examples, roughly half of which still use 2.x code and may not work. This time around we have test coverage for these examples, but many still need to be upgraded. To contribute first install the development dependencies:

$ npm install

Then run the acceptance tests:

$ make test-acceptance

Add some tests for an example, write some tests, make sure they work, then open a pull-request :)

That’s all for now! When I have time I’ll be rewriting the site, updating the wiki, and of course finishing the release. If there’s anything you find yourself doing often and think has a home in core let me know!

cheers!

EDIT: If you’re looking for docs we now have some auto-generated docs that you can view in your terminal. View this post.