New Stylus Features

Not so long ago I be­came a main­tainer (I'll write some­day later how this hap­pened and what ex­actly I do there, but it worth to men­tion that I'm a main­tainer, but the main de­vel­oper now is my col­league Mikhail Ko­repanov) for Sty­lus CSS pre­proces­sor. At the end of the last week, we re­leased a new ver­sion —0.41.0 —where we added some new fea­tures. And in the two ear­lier re­leases we added sup­port for the hashes and pol­ished it, so af­ter all this work it is now pos­si­ble to do a lot of in­ter­est­ing new things. In this ar­ti­cle, I'll ex­plain one new tech (You can go straight to its step-by-step ex­pla­na­tion, or to the re­sult­ing code) that is now pos­si­ble in the new Sty­lus, but I'll de­scribe the new fea­tures for a start.

Block mixins

Fi­nally! The thing that was miss­ing from Sty­lus for so long —the abil­ity to pass blocks of Sty­lus code into mix­ins and then use those blocks in­side the mix­ins' bodies.

The syn­tax for pass­ing the block is rather sim­ple: we call a mixin us­ing a + pre­fix, then we pass the block ei­ther in the curly braces, or us­ing a new in­dent level (as you could do with a lot of things in Sty­lus):

+foo()
  // The block we want to pass
  width: 10px
  height: 10px

Af­ter we passed the block to the mixin, this block be­comes avail­able in­side of it as a named ar­gu­ment —block. You can then use it any­where in­side the mixin us­ing an in­ter­po­la­tion (There is a pos­si­bil­ity we would add a way of us­ing it with­out in­ter­po­la­tion in the fu­ture though):

foo()
  width: 20px
  .foo
    {block}

Or we could pass this as a vari­able to the next mixin, or use it in any other way.

Any­way, if you've called a mixin above like this:

.bar
  +foo()
    padding: 0
    .baz
      height: 20px

You would get this:

.bar {
  width: 20px;
}
.bar .foo {
  padding: 0;
}
.bar .foo .baz {
  height: 20px;
}

With block mix­ins, we have a way of wrap­ping blocks with mix­ins and then wrap­ping them with any­thing. This fea­ture is of­ten used for han­dling me­dia queries, and my ex­am­ple that you'll see later in this ar­ti­cle is also from the RWD area.

Hashes

As I al­ready men­tioned, in the lat­est re­leases of Sty­lus we added (and pol­ished to a us­able state) hashes as a data type. Hashes are ob­jects with key-value pairs, and they look rather simple:

foo = {
  bar: 10px,
  raz: #fff,
  baz: {
    blah: blah
    '10%': yeah
  }
}

As you can see from this ex­am­ple, the syn­tax is sim­i­lar to the ob­jects in JavaScript: the key could be ei­ther an in­dent or a string, and any­thing could go into value, even nested hashes. An im­por­tant part: while you can use or­di­nary blocks with or with­out curly braces in Sty­lus, they are manda­tory for hashes, while the trail­ing com­mas are not (And as with all other op­tional syn­tax fea­tures of Sty­lus, you should use a con­sis­tent code style in your stylesheets. Oth­er­wise your code would be messy as hell).

Then, af­ter you de­fined a hash, you could add new prop­er­ties to it or re­de­fine old ones us­ing dots or square brackets:

foo.bar = 20px
foo['whatever'] = 'hello'

The dif­fer­ences are sim­ple: while you could use only idents with the dot syn­tax, with square brack­ets you could pass any string con­tain­ing any sym­bols, or use a vari­able in­stead. So, the brack­ets are more flex­i­ble, while the dot is not.

You can get the val­ues from the hash in the same way —ei­ther by us­ing a dot or us­ing the square brackets.

I won't de­scribe all the fea­tures of the hashes, I'll just men­tion that you can use the built-in length() func­tion with them, you can it­er­ate through them, use them in con­di­tions (if baz in foo), and there are also some built-in func­tions to work with hashes (keys(), val­ues(), merge()). And you can in­ter­po­late hashes into CSS.

selector() function

There is now one small but im­por­tant fea­ture in Sty­lus —se­lec­tor(). While you can con­struct com­plex se­lec­tors in Sty­lus by us­ing nested blocks, in­ter­po­la­tions, mix­ins and other things, you couldn't get the com­piled se­lec­tor, they only ex­isted in the com­piled CSS.

But now, us­ing se­lec­tor() func­tion that re­turns the cur­rently com­piled se­lec­tor, you could do a lot of use­ful things, like check the se­lec­tor for some­thing us­ing the match() func­tion, or use it for some­thing else. It is al­ready very use­ful, and it would be­come even more so in fu­ture re­leases.

As an ex­am­ple, you can take this small chunk of code:

if match(':(before|after)', selector())
  content: ''

Here we check if the se­lec­tor has any pseudo-el­e­ments in it and if so —we ap­ply the con­tent. This could be use­ful if we have some mixin, con­tain­ing styles that could be ap­plied both to a nor­mal el­e­ment and to a pseudo one.

Example with cached media queries

As a us­age ex­am­ple of the new Sty­lus fea­tures, I'll give you a so­lu­tion for one of those small, re­spon­sive web de­sign prob­lems: the code you need to write for dif­fer­ent view­port break­points. The prob­lem is that the syn­tax of me­dia queries could be rather long, so you could ei­ther use bub­bling me­dia queries which would re­sult in not ideal CSS, or, in the race for bytes, you'll need to write all the over­rides in one place, and that won't be very com­fort­able in a lot of sit­u­a­tions.

How­ever, in the new Sty­lus, with block mix­ins, hashes and the se­lec­tor() func­tion, you could work around this prob­lem (and solve some oth­ers on your way to it).

Briefly —we can cre­ate a mixin that could be used in­stead of me­dia queries and would cache the given blocks, com­bin­ing them by con­di­tions, so you could then out­put all of them us­ing the sec­ond function.

The only down­side of this method is the group­ing it­self —the se­lec­tors would be in a dif­fer­ent or­der, so the speci­ficity of the se­lec­tors could change.

For the start we need an ob­ject where we would store the cached blocks:

$media_cache = {}

Then we would need a mixin which we could use in­stead of me­dia queries; its ba­sic form would be this:

media($condition)
  unless $media_cache[$condition]
    $media_cache[$condition] = ()
  push($media_cache[$condition], block)

This mixin's logic is rather sim­ple: if we don't have a list of the blocks for a given con­di­tion, we ini­tial­ize it then we pass the block to this list.

It won't be enough for us ac­tu­ally: this mixin could be used only this way:

+media('(max-width:640px)')
  .foo
    display: block;

We could only pass full blocks to it, but couldn't use the bub­bling:

.foo
  +media('(max-width:640px)')
    display: block;

The code of our me­dia mixin doesn't know any­thing about the con­text, the se­lec­tor where we called it —yet. Here the new se­lec­tor() func­tion and an ex­tra helper mixin are re­quired, and with them me­dia mixin would look like this:

media($condition)
  helper($condition)
    unless $media_cache[$condition]
      $media_cache[$condition] = ()
    push($media_cache[$condition], block)

  +helper($condition)
    {selector()}
      {block}

To save the con­text we move the ini­tial code of this mixin in­side a helper mixin, then call it pass­ing the block in­side the in­ter­po­latedse­lec­tor().

So, as we now wrap the code with a mixin, it won't build au­to­mat­i­cally. We would need to call a func­tion that would take a cache and put all it con­tains where we call it (and it would be log­i­cal to call it at the end of our stylesheet):

apply_media_cache()
  for $media, $blocks in $media_cache
    @media $media
      for $block in $blocks
        {$block}

It is rather easy: we it­er­ate through the cache, tak­ing the con­di­tion —$me­dia, and the list of all the blocks that were called with it —$blocks, then we cre­ate the me­dia query with that con­di­tion and in­side of it it­er­ate through all the blocks, yield­ing all of them one by one.

So, if we would then call this func­tion at the end of the doc­ument:

apply_media_cache()

We would get what we want.

How­ever, there are a few things to im­prove in this func­tion: we do not want al­ways to write the paren­the­ses, and, ac­tu­ally, we won't want to write all those only screen and. Also, we would want to use some key­words in­stead of the lit­eral con­di­tions, like palm, portable, desk (I've taken the names from the great inuit.css frame­work by Harry Roberts) and so on. With those im­prove­ments and all the pre­vi­ous steps the re­sult­ing code would be this:

Resulting code

// Define the cache and the aliases
$media_cache = {}
$media_aliases = {
  palm:       '(max-width: 480px)'
  lap:        '(min-width: 481px) and (max-width: 1023px)'
  lap-and-up: '(min-width: 481px)'
  portable:   '(max-width: 1023px)'
  desk:       '(min-width: 1024px)'
  desk-wide:  '(min-width: 1200px)'
}

// Mixin for caching the blocks with the given conditions
media($condition)
  helper($condition)
    unless $media_cache[$condition]
      $media_cache[$condition] = ()
    push($media_cache[$condition], block)

  +helper($condition)
    {selector() + ''}
      {block}

// Function we would use to call all the cached styles
apply_media_cache()
  for $media, $blocks in $media_cache
    $media = unquote($media_aliases[$media] || $media)
    $media = '(%s)' % $media unless match('\(', $media)
    $media = 'only screen and %s' % $media
    @media $media
      for $block in $blocks
        {$block}

// Here would be our main styles, using the `media` mixin
// …

// Here we call all the cached styles
apply_media_cache()

Then we could write our stylesheets like this:

.foo
  width: 10px

  +media('lap')
    width: 20px

  +media('desk')
    width: 30px

  +media('min-width: 200px')
    width: 60px

.bar
  height: 10px

  +media('lap')
    height: 20px

  +media('desk')
    height: 30px

  +media('min-width: 200px')
    height: 50px

  +media('(min-width: 500px) and (max-width: 700px)')
    height: 50px

And get this re­sult af­ter­wards:

.foo {
  width: 10px;
}
.bar {
  height: 10px;
}
@media only screen and (min-width: 481px) and (max-width: 1023px) {
  .foo {
    width: 20px;
  }
  .bar {
    height: 20px;
  }
}
@media only screen and (min-width: 1024px) {
  .foo {
    width: 30px;
  }
  .bar {
    height: 30px;
  }
}
@media only screen and (min-width: 200px) {
  .foo {
    width: 60px;
  }
  .bar {
    height: 50px;
  }
}
@media only screen and (min-width: 500px) and (max-width: 700px) {
  .bar {
    height: 50px;
  }
}

In the re­sult­ing code, we can see that we added the hash with aliases, we can also call the mixin with con­di­tions lack­ing paren­theses.

By us­ing this code we can now use me­dia queries bub­bling any­where we want and don't even need to think about the ex­tra bytes —every­thing would be nicely grouped in­side non-dou­bling me­dia queries. All thanks to the new Sty­lus fea­tures.

Of course, this code is not ideal, and there could be a lot of ways to im­prove it, but my goal was to demon­strate the new fea­tures and how they work, af­ter all.