Skip to content

CSS-Polyfill-Preprocessor — It's A Myth

I want to share some thoughts on Myth.io — specifically its key idea of being a "CSS polyfill".

The short story is: Pretending to be CSS but not matching its power will confuse people.

Myth is a CSS Preprocessor (like Sass, LESS, Stylus or one of the other five thousand). It wants to set itself apart from the existing tools by claiming to be "like a CSS polyfill". Only that, it's not, not really. It uses the syntax of modern CSS, without being (remotely) able to offer the functionality of modern CSS.

At work we use Sass (Compass actually, but we're trying to get rid of the ruby dependency…). One of the many helpful things provided by a preprocessor has been the ability to use variables and do math (Number Operations) in "CSS".

Ambiguous Math

In Sass I can easily spot where a computation is happening. The first line, being invalid CSS, is handled by the preprocessor, the second line is handed to the browser.

div {
  width: 100px - 50px;
  width: calc(100% - 50px);
}

In Myth both statements look the same:

div {
  width: calc(100px - 50px);
  width: calc(100% - 50px);
}

Both Sass and Myth compile to the same CSS:

div {
  width: 50px;
  width: calc(100% - 50px);
}

But in Myth it is up to me — the developer — to figure out which way a calc() will behave. I don't think the argument type (pixel value vs. percent value) should determine that. I'm not sure why exactly, but this is ringing all sorts of alarm bells.

CSS Variables

Myth decided to use the CSS Custom Properties notation for their variables:

:root {
  var-width: 100px;
}
p {
  width: var(width);
}

compiles to

p {
  width: 100px;
}

There's not much of a difference comparing the example above to Sass:

$width: 100px;
p {
  width: $width;
}

But since we're talking about a polyfilling preprocessor, we have to take a deeper look beyond the syntax of CSS Variables. Comparing preprocessor variables to CSS Custom Properties is like comparing a Fiat 500 to a battle tank.

Source Order

Both Myth and Sass use variables like "replacing static placeholders". They disagree on the declaration/usage order, though. Sass interprets the source sequentially. Every variable declaration it encounters is pushed onto map. Every variable usage looks at the map. This is what we'd call "source order":

$width: 100px;
p {
  width: $width;
}
$width: 200px;

// yields: p { width: 100px; }

Myth, being a "CSS polyfill" complies with the "last valid declaration" character of CSS:

:root {
  var-width: 100px;
}
p {
  width: var(width);
}
:root {
  var-width: 200px;
}

// yields: p { width: 200px; }

I don't think this would be a problem in cleanly maintained source. To anyone coming from a traditional precompiler this might be a WTF-moment. But really, it's more like CSS works than how preprocessors tend to deal with things.

Mind The Cascade

You might've noticed that the examples declared variables in the :root block. That is because a CSS rule (property: value;) must be declared in a block — it can't be a direct child of the document. In other words: there are no global CSS rules. That is, until you meet the cascade. If you apply html { font-size: 20px; } that will also be the inherited font-size of body (and any other descendant element, unless it's overwritten somewhere). CSS Variables follow the same rule and thus allows the following:

:root {
  var-width: 100px;
}

div {
  width: var(width);
  background: red;
}

div.wide {
  var-width: 200px;
}

For any <div> the width would be 100px, for any <div class="wide"> the width would be 200px. The cascade takes care of this for us in the browser. If we were to accomplish the same thing in a preprocessor, we would have to analyze the declaration and usage of each variable, map the graphs and fold selectors into an output like:

div {
  width: 100px;
  background: red;
}
div.foo {
  width: 200px;
}

While only annoying to implement, this generates rather ugly CSS if your variable usage gets more complex than the example. Myth doesn't do it and forces you to declare variables in :root blocks. It's taking the easy way out.

Powerful API

You already know the DOM (Document Object Model). There's a similar thing for styles, the CSSOM (CSS Object Model). It gives you access to anything within your CSS. We've had a subset of this on a DOM Element basis for years, it's the style property. Of course CSS Variables are exposed through that API as well. You can easily change, add and remove variables at runtime. Consider the following CSS:

body {
  var-width: 100px;
  var-increment: 10;
}

div {
  width: var(width);
}

and add a bit of JavaScript:

var button = document.getElementById('make-wider');
var style = document.body.style;
button.addEventListener('click', function() {
  var width = style.var.get('width');
  var increment = style.var.get('increment');
  var incrementedWidth = parseInt(width) + parseInt(increment) + "px";
  style.var.set('width', incrementedWidth);
});

Try the fiddle (in Firefox Nightly). As you can see, CSS Variables might just change the way we work with CSS altogether. We can use them to move styling related configuration from JavaScript to CSS. We can update a single value rather than having to deal with classes, specificity and overwriting declarations all the time.

CSS Variables can be optimized in a way that classes and styles can't. The rendering engine is likely maintaining a map of variable declarations and uses within the CSS. It can maintain another map referencing the affected DOM elements. It doesn't have to restyle the entire DOM tree in order to find out which nodes have changed simply because we added a class to <body>. Using those maps, it knows exactly what changed where. I'm not saying this is an easy thing and will be available from the get go. I'm saying that I can see this avenue being pursued by our beloved performance gurus.

Anyway, Myth — in its current form — won't allow you to build upon this feature. All var(name) instances are replaced at compilation. They could've allowed that by generating output that looks a bit like this:

:root {
  var-width: 100px;
}
div {
  width: 100px;
  width: var(width);
}

Conclusion

  • it is not obvious if calc() is executed by the browser or the preprocessor
  • CSS Variable adhere to the cascade - Myth does not
  • CSS Variables are dynamic - Myth kills that feature

CSS Variables aren't too far away: CSS Variables in Firefox Nightly. In Chrome Canary you can enable them by activating Experimental Web Platform features in about:flags. We'll see this feature in stable releases in early 2014.

I looked into Myth because they made an interesting claim. A claim that the home page of Myth.io made me doubt. A claim I can (very much to my dissatisfaction) confirm is bollocks. I'm not saying Myth is bad, but I don't think it's going to work for me.

The problems with CSS Variables were explained in issue #10 and pretty much ignored, except for the one thing Tab suggested. I hope they figure things out and can live up to the "promise". I'll happily revisit the tool once they do.

Comments

Display comments as Linear | Threaded

Jens Grochtdreis on :

Jens GrochtdreisA polyfill in my book is a JavaScript, that mimics a technique which isn't implemented in a browser. If I understand myth correctly it isn't a polyfill but just another preprocessor.

It is good that it ain't a polyfill like prefixfree because I don't want to give the burden of modern CSS to the user if there is no need. And there is seldom need.

So myth.io is a preprocessor which does want to make a difference in marketing in claiming itself to be a postprocessor so that it wouldn't be compared to Sass.

It's a marketing-trick. If you aren't bought for Sass, Stylus or LESS you can try myth.io. All other developers: just ignore those folks and carry on. There's nothing important to see :-)

Sean on :

SeanThis article seems very one sided.

The point of Myth (so I've come to understand) is to provide a polyfill for devs to write modern CSS and still have it support older browsers. It's even in their tagline: "Myth is a preprocessor that lets you write pure CSS without having to worry about slow browser support".

You seem to be criticizing Myth for fitting into that role, and comparing it to CSS preprocessors (and native functionality) which exist to fit into roles that are different for different reasons.

For example, in your third point, you argue that "CSS Variables are dynamic - Myth kills that feature". You demonstrate an example using native CSS and var(), showing that CSS variables can be accessed and manipulated through JavaScript and noting that Myth cannot do this. While the two points in themselves are valid, you seem to be reaching the conclusion that this, in itself, is a bad thing.

Your second and third concluding statements show that you misunderstand the intent of this preprocessor. Myth does not use native var(), only the syntax, because of it's goal. Referring back to it's tagline (and main selling point), it's aim is to be a polyfiller. By preprocessing var(), it takes the need (and pressure for support) out of the browser, allowing the CSS to be rendered without potential issue of var() not being supported in whatever the user is using to view the page.

There's a multitude of articles online which comment on the vast amount of utilities web developers have at their fingertips. From frameworks to preprocessors, the issue no longer lies in a lack of utility, but more of the choice of what you use.

Your three points show that, whilst the initial points are true, this article reaches the wrong conclusions from them, and ends up bashing Myth quite heavily without taking into consideration what it exists for (even though this is mentioned in the introduction to this article), and comparing it to tools which perform better in the examples you use which are irrelevant to the niche which Myth tries to fit itself in to.

@Jens Grochtdreis

Myth doesn't seem to mention anywhere that it's a "postprocessor". You can see the above points as to why your comment also does not understand the concept of fitting a niche.

Rodney Rehm on :

Rodney RehmHey Sean,

Your second and third concluding statements show that you misunderstand the intent of this preprocessor. 

As has been pointed out, even by Ian himself, the original claim - being a CSS Polyfill - is simply not true. And yes, I like many others have been mislead by this promise. This article explains why this promise cannot be held up.

Myth does not use native var(), only the syntax, because of it's goal. 

By using the same syntax without providing the same functionality, they make the functionality inaccessible. I don't know how this can be made any more clear: If you process var() statically, it CANNOT be processed dynamically (without significant overhead to the output CSS). By using Myth - in its current form - you won't be able to use the real CSS Variables running in the browser.

From frameworks to preprocessors, the issue no longer lies in a lack of utility, but more of the choice of what you use.

Exactly. And what do you base any choice on? Numbers and facts, experience and taste. This article points out facts and presents a suggestion (not to use Myth) from derived from experience. It's nothing personal. I like Ian - as far as I got to know him. I like people that create things in order to improve the situation.

Sometimes you build something only to realize that what started out as a spectacular idea, turned out to be futile. I'm providing feedback. So the user is informed about what they're getting into. So the developers can look into fixing things.

Is this article a clear warning? Yes, intentionally. Will I write an equally detailed piece once Myth holds up to its promise? Hell yes!

The author does not allow comments to this entry