Let’s say there are rumblings from the deep (read: early Editor’s Drafts of potential future web tech specifications) that demonstrate some potential future code syntax. Say that syntax looks pretty awesome and we want it to be useable right now. That’s the idea behind some preprocessing these days.
Because this style of preprocessing has the vibe “write the code of the future and turn it into the code of today” it’s been popular to call them a postprocessor. I think that’s silly at best and confusing at worst. I’m going to keep calling it preprocessing. The process is exactly the same as preprocessing: you write code in a special syntax, it’s processed into regular CSS in a build step, the regular CSS is what the site uses. Preprocessing. I think postprocessing would be a better word for things like -prefix-free or other client-side polyfills.
The conversations of future-syntax preprocessing in CSS are connected to conversations about PostCSS. But PostCSS actually doesn’t do much CSS transformation on its own. David Clark explains it like this:
[PostCSS] does not alter your CSS. A plugin might do that. Or maybe it won’t. PostCSS plugins can do pretty much whatever they want with the parsed CSS. One plugin could enable variables, or some other useful language extension. Another could change all of your as to ks. Another could log a warning whenever you use an ID selector. Another could add Masonic ASCII art to the top of your stylesheets. Another could count all the times you use float declarations.
So the future-syntax preprocessing doesn’t come directly from PostCSS, it comes from PostCSS plugins. There is one plugin in particular that focuses on this: cssnext. cssnext is actually a collection of PostCSS plugins that are all focused on future-syntax preprocessing. But cssnext is not alone. There are other preprocessors and plugins that operate like this. On the JavaScript side, preprocessors like Babel are in the same boat.
I see a few problems with the idea of future-syntax preprocessing.
But to be clear before we get started: you do you. If you use and love preprocessing like this, that’s great. Feel free to talk about it in the comments. And if you work on these preprocessor projects, wonderful. You’re probably helping the web more than I ever will by allowing people to play with future syntaxes early.
We support PostCSS (including cssnext) on CodePen. We support Babel on CodePen. The last thing in the world I want to do is hamper ideas.
I just think there are a few things to consider here that might cause trouble for folks picking them up to use on real, long-term, production products. At least, it’s worth having a conversation about.
One Tiny Example
At the risk of getting bogged down with just one sample, here’s a sample of how future-syntax preprocessing might work in CSS. This is how CSS variables is looking in an Editor’s Draft right now:
:root {
--brandColor: #F06D06;
}
.main-header {
background: var(--brandColor);
}
There is a variety of CSS preprocessor thingies™ that will process that for you. The result is likely:
.main-header {
background: #F06D06;
}
Problem #1: Future Syntax Changes
Firefox is actually shipping with support for the above example of native CSS variables right now. But they’ve had to do some weaving. It first landed in v29 with the var-
prefix instead of --
. But then the spec changed. So they had to change the implementation.
That means CSS preprocessing that was supporting this had to do some weaving too. That means some tough changes and forks-in-the-road for the preprocessor authors.
Choices for the preprocessor developers
- Do you support the new syntax when it changes?
- Yes = keeps in the spirit of being a future-syntax preprocessor.
- No = now you’re just a preprocessor based on an orphaned syntax.
- Do you support the old syntax too?
- Yes = runs the risk of a confusing syntax and a bloated code base that has to handle things multiple ways.
- No = you’ll break people’s code.
Not great choices. When cssnext faces changes, they output warnings first, then deprecate the old syntax in the next version.
cssnext: Previously @custom-selector were working with and without pseudo syntax ‘:’. Now you must use ‘@custom-selector :–{name}’ syntax instead of ‘@custom-selector –{name}’. The support of syntax without ‘:’ and this warning will be remove in the next major release.
Choices for the preprocessor users
If you’re a user of a future-syntax preprocessor, you have to accept that you’ll face breaking changes that come along somewhat randomly. That’s at odds with how most other preprocessors work, where the syntax is made up and purposely different from native CSS syntax (future or not).
- Do you update code to the new syntax or not? If the preprocessor developers deprecate the old syntax, you either have to update your code or never update versions.
- Do you have a choice? If the preprocessor developers stay with the old syntax or support both versions, you could leave your code alone. You’ll have to decide how dedicated you are to the new syntax. If the developers go new-syntax-only, you’ll have to decide to either update your code or give up and stop that particular bit of preprocessing.
Problem #2: Some Features Can’t Be Replicated
The CSS variables example we started with can demonstrate this as well. The way native CSS variables are going to work in the browser isn’t the same as just replacing variable names with values in the final CSS. With native CSS variables, if you change the value of the variable with JavaScript, everything referencing that variable will update accordingly. It remains dynamic in a way that replacing those values with static code would not.
CSS calc()
is another example. You can’t just replace all instances with calc()
with a processed value. What 100% - 20px
computes to is impossible to know by a preprocessor. The browser needs to evaluate that, and keep evaluating it as the the environment changes. If you just need to to some static math, you should probably just do some math and not leave it inside calc()
.
The PostCSS plugin for calc() is smart in that it “reduces calc() references whenever it’s possible.” – meaning if what you have written could have been static, it makes it static. That goes to show that while we’re having a wide-in-scope conversation here, a discussion about usage on a particular project would be more narrow-in-scope, focusing on how particular plugins solve particular needs you have.
The plugin for separating the transform properties says specifically:
Once these new properties are supported natively, you can also use them to style transforms across multiple rules without overwriting a previous rule’s transforms. Unfortunately, I cannot predict how your CSS rules will be inherited without also knowing your DOM. Therefore, this particularly useful feature cannot be simulated at this time.
So if you start using this now, you don’t get the primary benefit of the syntax existing. And when you remove the plugin, the code will function entirely differently.
It’s not that the plugin is bad. It does good stuff. It makes authoring more intuitive. It makes version control difs more useful. It signals interest to browser feature implementers there is interest here. It alters code to use the most performant techniques.
It’s just feels a little dangerous and like you better know what you’re doing if you go down this road.
Problem #3: The Cutover
If the reason you’re attracted to using future-syntax processors is that you think that someday you can ditch the preprocessor and have totally valid CSS, that’s going to be tough. It might actually delay that moment. Say you want to start actually shipping some future-syntax code because some browsers are starting to support it. But your preprocessor is still processing it, and you can’t remove the preprocessor because a lot of browsers aren’t supporting it yet.
Maybe the preprocessor can start outputting code that does both the future syntax and a fallback? Maybe that’s impossible? Maybe it could be the developer is unresponsive or uninterested?
And what if a future syntax never comes about? Then the cutover may never happen.
Personally I’m OK with never cutting over. I think a level of CSS abstraction (the preprocessing step) makes sense to always be there. You need a build step anyway (concatenation, compression, other deployment-prep) might as well preprocess as well.
Plus there are concepts that I think belong in an abstraction of a language and not the language itself. Variables could be an example here again. Preprocessor variables and native variables could co-exist and be useful in their own ways. If native CSS could do everything ever dreamed up in a preprocessor, it would be slow, complicated, and likely wouldn’t have seen the success that CSS has had as a language.
Problem #4: Code Portability
I’ve heard it expressed that future-syntax code is safer to write because it’s just CSS and therefore more portable. Meaning you can share it with others, share it between projects, write about it more easily, etc.
If we’re talking about current, native CSS, that would be true. But remember that PostCSS is a large plugin ecosystem and you use it by piecemealing together plugins you’re interested in using. That means any given project is using a different configuration of plugins. Any given chunk of code may not work in a different project using different plugins. Or it might work if it’s only reliant on plugins that the other project happens to also have. Or it might work differently if the other project has different plugins but happens to accept that same authored syntax.
It seems like code is actually less portable when written in a system like this, as compared to code written in a system with universal abstractions that are purposefully different than native CSS, future or otherwise. It seems people highly involved in both Sass and CSS agree on this.
So
What do you think? Am I totally wrong that there is anything to be worried about here? Do you have any personal experience to share?
Is it just me or
var(--brandColor);
looks just… wrong.Yup. Couldn’t agree more.
This looks too close to Javascript’s pre-decrement syntax. This would possibly create even more confusion.
Me too, W3C suggested strange syntax (but I understand why thay do it). This is why PostCSS has second plugin for variables: https://github.com/postcss/postcss-simple-vars
Me too… seems crazy to invent another way of declaring and reusing variables.
I totally agree with you on these issues, the idea of portability between devs is laughable when you have a large project with multiple dispersed teams. You would have to lock them all down to particular preprocessors etc, plus it gives you a greater burden hiring staff with the expectant skills. All that before you even look it the issue of programs such as Babel trying to predict the future by pre-implementing a spec that constantly changes.
There are some advantages though, as they can also improve productivity of a dev. Plus with projects like Babel now being fairly solidified in what it offers and how stable a feature is before pushing it forward so less of an issue of future syntax changes. So maybe if the developers of these projects can show that type of restraint maybe problem 1 would be less of a worry.
Sass/Less could be argued they will always have a lack of cut over, as they fixed a problem before the idea of a future implementation came about, so they have gone their own direction and dont have to worry about future syntax changes. And as you say its better to have something to improve CSS than nothing.
“The PostCSS plugin for calc() is smart in that it “reduces calc() references whenever it’s possible.” – meaning if what you have written could have been static, it makes it static”
Anyone doing this (as in actually writing something like calc(100px+10px)) should not be writing anything, pre, post, current.. anything!
You’re quick to judge. Maybe you shouldn’t be writing anything.
Consider this, which can compiled to a static value:
Is that the only way to use math with CSS variables? As somebody said above the syntax is horrible. What’s wrong with the SCSS syntax for variables and math?
width: $sidebarWidth + $contentWidth;
Why would you need calc?
Jon, technically you can add “SCSS math syntax” to PostCSS too, but nobody wrote a plugin for today.
I don’t think that the calc() plugin even belongs to cssnext, it’s just an optimizer. It doesn’t polyfill the calc() function, it just removes unnecessary ones.
I can’t speak to the CSS story as much as the JS one, but when it comes to Babel, they take steps to disable the more cutting edge features (ES7/ES2016), making them opt-in (see http://babeljs.io/docs/usage/experimental/).
That doesn’t mean something might not change, but it does make it more unlikely.
And then when talking about Babel and ES6 (or ES2015, if you will), that spec has been finalized, and at this point would not only be fine to embrace, it would be wise to start using it now to learn the new features.
Yes, it is likely we will always be using preprocessors (more commonly called transpilers in JS-land) for JavaScript in the foreseeable future, because there will likely pretty much always be that “next feature.” But there will come a time where we can stop compiling for ES5 and start compiling to ES6 and then those features would be handled natively, etc.
Exactly what I was about to write.
I’m currently writing ES2015 JavaScript for production use. Of course it currently needs to be transpiled to ES5 with Babel (or equivalent [if any]).
Like you said, this allows me to write clean and future-proof code. Once the ES2015 will be all-green on the browser stack, I can just stop traspiling it.
Or more likely, in the meantime I would start refactoring the code to use ES2016 syntax (once finalised) and start transpiring from 2016 to 2015.
It’s basically a way to always be ahead of the curve.
Thanks Chris.
I couldn’t agree more with you.
I started to use PostCSS more than one year ago when it was in early stage (v0.3), and I’ve created the first plugins’ package tool. I never liked this future-syntax thing, but I’ve experimented a bit and created some module because, you know, users demand.
Now, I think the best workflow is to combine preprocessors and some modules from PostCSS.
Personally, I like the implicitness of postprocessors (to silently improve CSS, eg. prefixes, fallbacks, etc.) and the explicitness of standard preprocessors (use their own language to generate CSS). That’s why I’ve combined them in Pleeease.
Basically, postprocessors replace librairies like Compass or Bourbon.
I think the main thing people are often forgetting is that if you start using preprocessors you will NEVER be happy with plain CSS in the future.
Because as soon as all those features that you are looking for have reached Recommendation status and have landed in all relevant browsers, there will be a whole bunch of new cool stuff on the horizon that you will want to use and that will only be possible with a (new) preprocessor.
Yeah. We’ll always be using preprocessors, let’s face it. I don’t get why that’s a bad thing.
I’ve been using Myth for about a year now and have been quite happy with it. It seems to do all of the big things that PostCSS + nextcc does, but all as a single package. Plugins might be nice in certain circumstances, but being a single package like this lessens the issue of portability.
I think that your concerns are valid, but also missing the point. Languages and tools are constantly changing. There will probably be a day when LESS, etc. are all replaced by something else and projects will have to update to the latest and greatest or be stuck using outdated tools. Not even HTML & CSS are safe from this.
But things such as PostCSS & Myth offer the assurance that, even if they are completely abandoned, your CSS will still be worth something. Because it is “pure CSS”, that means that it will always be possible to build or use a replacement without much concern with compatibility.
What would you think of some preprocessor if it were able to use LESS or SCSS, the source itself was able to be actually used in modern browsers, and it didn’t require learning some syntax that didn’t exist anywhere else?
When SCSS is gone, you’ll have no choice but to rewrite everything. When PostCSS and anything like it are gone, you’re left with CSS.
Myth is totally same as cssnext. It has Rework instead of PostCSS inside. And Myth is plugin pack of many small plugins, 100% like cssnext.
But Rework is dead project right now. So there is no future right now for Myth too.
cssnext is like Myth but:
is an active project
is way faster
has far less bugs
has far more features
I feel that some pre-/post-processors are toys for demos more so than tools for production use. Like the
calc()
processor – if you have expressions that can be evaluated ahead of time, they don’t belong incalc()
at all, they belong in your Sass/LESS/Stylus/etc.Does nextcss actually use the working drafts of CSS? In the case of Babel, it only processes code that will not change in the next spec (ES6). I think that nextcss should only process agreed-upon CSS that is not going to change (final draft, or whatever it’s called).
This still doesn’t solve the problem of having a bunch of plugins to add preprocessor-like features.
Correct me if I’m wrong of course.
Babel also supports parts of ES2017 and even more. These things are experimental and not set in stone yet.
cssnext is based on draft. A
stage
flag will allow you soon to choose what level you want to support https://github.com/cssnext/cssnext/issues/100Well done, Chris!
Probably cssnext and related (future-CSS) plugins would benefit from differentiating their features based on the maturity of the related spec, as Babel does — documenting each feature’s maturity, helping users to restrict themselves to specific levels, etc. Of course, Babel has the advantage that ES6/ES2015 is “approved,” a very real thing now, whereas the CSS community seems much less clear on the status of these various CSS drafts under consideration. Once some of those start approaching finalization, the game changes quite a bit, right?
cssnext and related projects are very active and open to input, so everybody should go help out if you have ideas for addressing these concerns!
Also: I like the line “you better know what you’re doing if you go down this road.” I think that at this point many would consider Sass/Less easier consumables for people who don’t want to think that much about their tools; while PostCSS provides more possibilities, provided you want to think actively about those possibilities, experiment a little, etc.
stage
flag is coming https://github.com/cssnext/cssnext/issues/100The problem of supporting “future” syntaxes is always the same: we don’t know the future, we will never be able to, and most of the times, we bet on it (badly of course).
I think we should be more responsible looking for these issues: just look at the prefixes problems (Firefox has dropped support of old gradient syntax, and they’re going to support it again, due to a lot of compatibility reasons) we should learn from these errors and stop predicting the future.
The past is helping us everyday to make a better future. For example, React and Babel are showing us (like some others projects) that we are not predicting the future, we are implementing it. And this process helps to make specs even better. Just sayin’.
At this point in time, most CSS polyfills are static polyfills. They offer “future-proof” syntactic benefits while lacking the dynamic inheritance benefits of a native implementation. This is the case with variables and transform shorthands.
This is a serious trade off, but nothing to necessarily throw out the baby with the bathwater. We used to show hesitance for abstracted languages likes Less, Sass, and Stylus. We used to show hesitance for JavaScript polyfills. We had examples to back up both. Now we show hesitance for PostCSS plugins. These are the same hesitancies, which are healthy, and IMHO mean we’re moving forward.
Great article, sums up my own thoughts of PostCSS very well. Two things I would add:
In #3 the key point for me would be that once feature X is well supported and you want to remove PostCSS, there will almost certainly be more new features (e.g. in a CSS5 spec) that you now want to support. So the “cutover” never ends.
Also, perhaps the opposite to #2, there are many other features of Sass that aren’t being replicated in new CSS any time soon (nesting, mixins, color functions).
I agree completely, but I have one qualm. I don’t think the term postprocessor is all that silly or confusing.
I’m not sure where the idea that a postprocessor is about future specs comes from, but I’ve always understood it the way it was introduced here on CSS Tricks, as a specific type of preprocessor that operates on valid CSS files, instead of one that compiles a special syntax into CSS.
I don’t care about the word on it’s own, I only care about honoring the author’s intent. The only postprocessor I use is autoprefixer, and if Andrey Sitnik wants to call it a postprocessor, that’s what I’m gonna call it too.
Ouh. Chris is right. “Postprocessor” was a bad term :). I try to avoid it.
Haha. OK! The war is over. Preprocessor it is.
Yeah, so lets do all that processing in-browser, screw the users, their battery life, the UI responsiveness, the rest of the site, lets stop using SASS/SCSS, and do away with server-side processing, put everything into the browser, business-intelligence, our algorithms, graphic design, everything. If we work real hard, we can focus on a new version of This
What?
As creator of cssnext, I have to admit that people should be careful indeed on what they are going to use.
That said here some others things I want to quickly throw (because instead of talking too much, I need to improve cssnext):
npm --save(-dev)
installs by default using the caret range (^), so this means you can get new features or bug fixes automatically). For breaking changes you need to upgrade on purpose (and so, you need to read the CHANGELOG). But, like it has been done, and will be again if needed), we can offer to our tool UX and provide easily instructions on how to upgrade your code if needed. We might even propose a script to make the transition even more easy if a search and replace (powered by regex) might be not enough.Totally agree on this. I agree that making your own custom CSS extensions is a bad idea.
very sincere response, but weakness to author’s convincing argument.
cssnext won’t become the babel in javascript, beacuse they are in different probelem domain.
css lack the ability 【Abstraction】, but javascript doesn’t .
I remember when Chris didn’t like the idea of Sass too.
Haha!
Ugh. Yeah, that’s a problem. I know this, what ever happens to make CSS annoying, someone smarter than me will find a way to create a build/pre/post process around it. The need for these three processes expose the level of detail/control we needed to write powerful CSS these days.
I agree, having already come to these conclusions regarding portability particularly because of the fickle nature of W3 specs.
The argument doesn’t apply to people writing ES6/7, because the ecma body is much tighter I believe.
My response to this post :) http://twin.github.io/css-preprocessing-drama/
I’ve been thinking the same thing, and I’ve thought similar things about the JavaScript ecosystem. A lot of people use Babel to enable ES2015/16 code, but I know that some things can’t be converted to ES5, and it scares me to think that at some point in the future I might tell Babel to start compiling everything to ES2015 code while I use ES2018 code (or something) and I’ll find that some things don’t work the way I expected because the compiled code worked differently than the ES2015 spec. There are other things like Decorators and Async Functions from ES2016+ that I want to use right now as well that I can’t guarantee will remain the same by the time they’ve gone through the whole spec process.
Then I heard that you can start plugging other things into Babel (PostCSS style) to enable other features, which made me think something very similar to what you were talking about in your last section about portability. Not only is it harder to share, but if you’re doing it one way at a company and then switch to another company that is using a different set of plugins, your skills may not transfer over. This isn’t terrible, since it’s just like learning a new library or framework because the different companies settled on different ones.
Another difficult situation is that you may get used to an abstraction/feature that simplifies how you code certain things, but then you go somewhere where you’re not allowed to use that abstraction/feature (ES2016 async functions or ES2015 classes are great examples) and you haven’t learned how to do it properly in ES5, you may not have a clue how to do it the “old” way. These abstractions require more training and better interviewing to find out what a developer REALLY knows.
The big difference between ES2015 and CSS4 (?) that this post fails to acknowledge is that the ES2015 draft is AFAIK finalized, so there will be little to no changes in the spec. This means that it would be pretty wise to start using the new syntax.
If some features can’t be converted to ES5, you shouldn’t use them, that’s why you should always use this as a reference. Babel docs do a good job of warning you if a feature is experimental, so you’re usually not clueless if you make a mistake.
ES2015 isn’t some abstraction language like Sass. Not only is it not going anywhere, it’s eventually going to be everywhere, so using these features shouldn’t be a forbidden fruit. Just remember
forEach
, do you still want to keep polishing yourfor
loop skills?Either you have issues using these tools or you don’t. I’ve been using Babel for quite a while now and I only love it more. Especially because it’s a much better alternative to CoffeeScript (which is an abstraction language).
Don’t you guys sometimes feels like you don’t need that “many” tools when it comes to write CSS. I like SASS for things such as variables, dividing files into parts that you can include to reduce the number of HTTP request and so on. But I think it’s important to remember that what’s served to the user (and the browser) is plain CSS. I feel like, most likely, CSS generated by postcss and etc isn’t as good as vanilla CSS you would write. I’ve seen SASS do some crazy thing when he outputted the CSS. It still worked very well on the website, but the file was longer then if I would’ve written it on my own. For this reason I’ve decided to use SASS for things that makes me save time and that I’m 100% sure will generate exactly what I’d want, for variables, includes and things that improve the performance of my website and that will makes my CSS better. I don’t feel like we need that many tools to compile good ol’ css… I kinda love to have full control of what’s served to the users of my websites and how the CSS will be outputted
I agree, that’s why I think e.g.
@extend
is so dangerous that it should hardly ever be used. But the logic is complicated, say, vendor prefixes. Do you really want write all those prefixes by yourself? I think Autoprefixer has become a universally standard part of the toolchain and because it’s a PostCSS plugin, you can easily add other plugins without complicating your workflow. I agree that you should mostly be writing plain CSS, even when you’re writing in Sass, people write some really heavy Sass stuff which are then hard to decipher.I have to agree with you that that plugin such as prefixer is very useful and can save you time. You are right, I think I should start using PostCSS the same way I use SASS. For the tools that can enchance my website performance and makes me save me time that I know will output exactly the CSS I’d like.
I’ve seen some people talking about the necessity of all the front-end tools and they’re somewhat right. This article reminded me of the Charlie Hebdo Attacks in Paris: their website needed to be altered that same day, so things had to go fast. The ‘shocking’ reality was that somebody just hand-coded the whole thing in HTML3, probably without any tool. Let us thus not forget that our actual goal is outputting plain HTML, CSS and maybe some JS.
My opinion about the preprocessor vs. postprocessor thing…
I’d argue that PostCSS is a postprocessor, unlike Sass or Less which are preprocessors, and here’s why. When it comes to Sass and Less, you must run the code through the preprocessor first before it becomes valid CSS (spec and grammar-wise). However with PostCSS the CSS is already valid, so you could still serve it to the browser even before giving it to PostCSS.
So the line between pre and post processing is the same line at which the CSS is standard to the spec. So adding vendor prefixes is post processing IMO, but custom grammar for variables, mixins, etc… is preprocessing.
With that said, there are some plugins you could write with PostCSS that turns it into a preprocessor… so maybe in the end it’s most accurate to say PostCSS isn’t one or the other, but your usage of it is. Which is kind of the point, since it does as much or as little as you want it to.
Of course, this distinction is pretty silly and it doesn’t really matter what it’s called.
PostCSS isn’t necessarily working on valid CSS. Part of the point of the article is that there are PostCSS plugins that allow you to use new/different syntax that isn’t valid CSS, so it could be used as a preprocessor. And of course since there are tons of PostCSS plugins out there adding this syntax, there is no single large standard or document to describe the features that you are adding to you CSS when you use multiple plugins at once.
I feel like was kind of how it was “marketed” in the past, but it’s totally not true. PostCSS is just a parser. Plugins can encourage process valid CSS, or totally made up random gibberish syntax, and there is plenty of that happening.
I mostly agree, except that these concepts are hard enough to grasp as is, splitting something like preprocessing into re-named sub-genres seems like a loss.