Postcss: Outputting JavaScript from PostCSS

Created on 5 Aug 2015  Â·  37Comments  Â·  Source: postcss/postcss

I was working on FunCSS for the last 6 months, which is a project with similar aims to PostCSS:

  • A language with standard CSS syntax
  • Future CSS now, compiled to polyfilled CSS
  • Custom plugins within CSS syntax

However, it also supports generating JavaScript for features that cannot be calculated compile time (e.g. calc(50%-20px)) and support for custom JavaScript functions that run on the client side (e.g. background-color: getColorFromDatabase(). It is in alpha phase.

I just stumbled upon PostCSS and I see that it already has a big ecosystem, similar to what I imagined for FunCSS. I'm thinking about somehow maybe porting FunCSS to PostCSS.

My question is: do you have any plans for including a mechanism for outputting JavaScript code from PostCSS? Or any ideas or thoughts about this possible integration of the two projects?

question

Most helpful comment

@MoOx

  1. And what is a point for Wikipedia to make compliated frontend with React prerender? React is not a universal solution for all websites. It is just a great tool for SPA, but we is not only SPA. SPA is great to create information, but most of users want to read information, not to create it.
  2. I was born in USSR and I know too much about “all this problem will be fixed in future, just belive in us” ;). Rendering perfomance is a most important thing. Data calculation perfomance can be fixed by loader in UI. But slow FPS is a main problem.

React Style is solveing right problems. But it solves it in wrong way. We should keep looking better solution.

All 37 comments

Awesome project! I will tweet about it in Friday :-).

PostCSS API returns not a CSS string, but a Result class. So you can just add custom properties to this object.

Or some postcss-funcss plugin can accept callback, which will receive JS content.

I think merge is good idea. PostCSS has one of the best CSS parser. So you will get performance improvement and stable parser, which was tested on big Autoprefixer auditory. In other hand PostCSS ecosystem will became closer to main PostCSS goal: stop current CSS stagnation and move CSS forward.

How FunCSS works inside? Let's think what I can add to PostCSS core for you. At least, you will need a function and selector parsers and event based API, we are thinking about them right now (sorry, I can't get your direct links to issue right now, because I am not in the city).

I haven't updated the Readme for a long time, but I could even do the first release now. If you want to look into the code, you should look at the devel branch.

Very briefly, FunCSS works like this:

  • Take source code, parse with CSS parser, make AST
  • Interpret AST to create a semantical representation. This includes:

    • inserting CSS declarations into tables.

    • parsing and interpreting function definitions

    • type-inferring property values and turning them into a data flow graph, while substituting custom functions with their definitions

  • Optimize
  • Convert the semantical representation to an abstract tree of JavaScript code. Property value dataflow graphs are converted to JavaScript code fragments that return CSS code.
  • Optimize
  • Serialize to JS code.
  • Beautify
  • Run browserify to include runtime libs and dependencies

So all the output of FunCSS is only JavaScript code that runs on the client and injects CSS code into the site.

Currently FunCSS doesn't have source maps, so that's also something that I could make good use of from PostCSS.

An important difference that I see is that FunCSS is a statically typed language, with its pros and cons. To work properly, it _must_ know the type of every expression to work properly, and also the allowed values of every usable CSS property. As a consequence, however, it will not compile counter-increment: rebeccapurple to an rgb representation (which cssnext does), because it knows that counter-increment is of type <ident>, not <color>.

FunCSS treats function definitions as type extensions. E.g. a function definition with the signature myfunction(x:number):color is interpreted as an extension of the <color> type with new possible values, myfunction(...) where ... is a number. So just as scale(0.5) is a possible value of <transform>, myfunction(0.5) will be a possible value of <color>. But myfunction(0.5) will be replaced with its function body, while scale(0.5) will be left as it is.

So yes, I also thought about dropping my own parser, and implementing FunCSS as a PostCSS plugin pack. Thanks for your help in advance, and yes, let's think about how this could be made and what changes are required in the core.

A major question to decide: How JavaScript output should be organized?

  1. PostCSS core should handle JavaScript output?
  2. FunCSS should have a custom way to handle JavaScript output?
  3. We should create a standard postcss-javascript-output plugin that handles JavaScript output, and FunCSS should depend on this.

For first sight, I prefer 3.

Now I updated the Readme and merged it to master.

Note, that current PostCSS parse is a dumb parser. It parses declarations values and selectors only a strings. But you can use PostCSS selector parser to parse nodes, that you need (it could be faster rather then parse evverything).

So static type is not a problem for PostCSS, because PostCSS does not to try to understand meaning of CSS. You can check types in you next values parser.

Yeap, you will get source maps for free if you will transform CSS by PostCSS. Also you can write a code to generate sources map for JS.

So postcss-javascript-output looks good. We can add source map support there (I can help you there).

Cool! Thanks!

I've made an initial version of postcss-javascript-output. Can you have a look at it? It resulted _really_ complicated. (Just kidding:)

I suppose we will need to somehow add support for other environments too (Gulp, CLI, Grunt etc)

I created few issues, but what this line mean:

if ( result.map ) fs.writeFileSync('app.css.map', result.map);

@cie I rethought about API (sorry that I get this ideas only now):

  1. FunCSS can use just result.messages to and put there { type: 'js', content: [string], map: [SourceMapGenerator] }.
  2. It is not a problem to ask gulp-postcss, etc plugins to support JS messages from result.messages.
  3. Maybe we need different CSS-with-JS meta package, just to generates source map. It can use some API like this:
var addJs = require('postcss-js');

module.export = postcss.plugin('postcss-funcss', function () {
    return function (css, result) {
        addJs(result, jsLine1, node1);
        addJs(result, jsLine2, node2);
    }
});

This CSS-with-JS meta package will append strings to JsMessage.content and generates JsMessage.map.

What do you think?

You are really doing weird stuff guys. Maybe you should consider instead making styles from JS.
What you are doing seems really dirty...

@MoOx of course, CSS with JS is not best thing for perfomance. But sometimes we have a tasks, that is not so critical for perfomance (for example, real calc() polyfill). Or sometimes we want to create proof-on-concept for some new CSS spec, that is impossible on pure CSS. So I think we havve user cases for it.

Why you think it is dirty?

Using a string? seriously?
The module that want to be a PoC should provide a JS polyfill directly. postcss runner should not handle js, this is just crazy to think it's a good idea.

I agree with @MoOx. You are putting some JS object on the result class to output a JS file? And then the JS file has to be quote-delimited? What's wrong with browserify? Or webpack?

IMO this encourages some horrible anti-patterns, and I don't see what relevance it has to PostCSS as it doesn't manipulate the existing AST.

@ben-eb @MoOx do you understand that FunCSS is a tool to add some JS expressions to CSS?

For example, we can add a component media queries to CSS like @media (max-this-block-width: 300px).

Indeed, you can achieve this functionality without even loading PostCSS:

var result = "console.log('hello world');";
require('fs').writeFileSync('app.js', result);

You should more think about serious issue like #434 or #477 instead of discussing this crazy approch.
Or find a way to make a bridge for PostCSS > inline styles, it's clearly a better approach and will provide a real (and smart) way to add polyfill directly into a JS env.

PostCSS should work on a method to transform AST as JS object that can be easily consumed as inline styles. This way we work on client side the right way, just in JS, not with a sort of combo that would involve css + parser + runtime transformations (js) + injection of modifications into dom.

Element queries are very easily doable from JS directly, without an overkill combo like I just listed.

@ben-eb your example is wrong, FunCSS is about generates app.js based on some custom syntax construction inside a CSS file.

For example we have CSS:

@media (max-this-block-width: 300px) {
  .el { color: red }
}

And FunCSS can compiles it to something like:

.el.uniq-class-274758 {
  color: red
}
document.resize(function () {
    if ( el.innerWidth <= 300 ) {
        el.classList.add('uniq-class-274758');
    } else {
        el.classList.remove('uniq-class-274758');
    }
})

Here you could do the exact same thing with inline styles. Just sayin'

But without a weird combo.

@MoOx Inline Styles has many disadvantages:

  1. It works only with client rendering.
  2. It slow.
  3. JS object is a bad syntax for styles.

I do not say, that FunCSS is a only was to make a bridge between JS and CSS. Of course, we have other solutions. For example, one of the goals for PostCSS 5.0 custom syntaxes is to work with JS objects of properties.

But we should experiment with different way to find a best one. Thinking that we have only one right way is religious way to stagnation.

  1. Your code above will obviously not work without JS.
  2. No inlines styles are not slow. Where did you see that? Any bench? Would like think Facebook will use inline styles if they were slow?
  3. JS object are much powerful because you can use pretty much whatever you want (real JS variables, any library for color manipulation etc. And you can help yourself with some kind of abstraction https://github.com/bloodyowl/stile.

Inline styles are the complete opposite of a religious way.
Most people are sayin "you are crazy with your inline styles, please die", like in the old times:

  • "hey, the earth is not flat you know"
  • "this guy is crazy, we should burn him now"

Anyway, do whatever you want, I hope you will learn from your mistakes ;)

@cie but maybe really making common PostCSS plugin from FunCSS is a overengineering right now.

The fastest way is just use PostCSS inside a FunCSS. Like:

var postcss = require('postcss');

var root = postcss.parse(css);
// Make FunCSS stuff with root

I'm not sure that I follow your example. We can do that without attaching a JS property to PostCSS in the first place.

@MoOx

  1. Yeap, element media query will not be work without JS, but component will be usable anyway, just not so fancy looking. Think about Wikipedia, they provide information, not a UI. They do not need client rendering.
  2. Class vs. style benchmark: changing a node.style[prop] is a 84% slower rather than changing node.className. Adding a node with inline styles vs. with class: DOM manipulations with node with inline styles i 14% slower. Also do not forget about bad perfomance of el.onMouseOver rather than .el:hover.
  3. JS is more powerful, agree. But it is not more readable, many unnecessary symbols.

To be clear: inline styles can be done from the server side.

This perfs will obviously be improved when more and more people will work with inline styles ;)

Also, when you use universal JS (isomorphic), it's not a problem so the client can catch up when sync the UI.

@MoOx

  1. And what is a point for Wikipedia to make compliated frontend with React prerender? React is not a universal solution for all websites. It is just a great tool for SPA, but we is not only SPA. SPA is great to create information, but most of users want to read information, not to create it.
  2. I was born in USSR and I know too much about “all this problem will be fixed in future, just belive in us” ;). Rendering perfomance is a most important thing. Data calculation perfomance can be fixed by loader in UI. But slow FPS is a main problem.

React Style is solveing right problems. But it solves it in wrong way. We should keep looking better solution.

:) Thanks guys for this fervent discussion :) And thanks @ai for eagerly defending my project

Let me also give my points:

  • React's in-JS styles is a beautiful solution, it solves many problems from the how-to-combine-CSS-and-JS-data topic. However, React is for software engineers. I want a solution for designers who know CSS and does not know JavaScript.
  • JavaScript is a powerful language. Some say it's a bit ugly, but it can do anything. CSS is a beautiful language, with few power. But it has some powerful features that are not found in JavaScript: selectors, percentage values, physical dimensions, cascading rules, automatic updates when the DOM changes, automatically handled transitions. I would like to (ab)use these.
  • CSS with its new features (variables, new media queries, custom media queries, custom selectors...) is a beautiful language with somewhat more power. And cssnext brings this to present CSS! Cool! I want these too.
  • However, I also want custom selectors that are defined as a JS expression (e.g. to polyfill :out-of-range), custom functions defined in JavaScript (e.g. to polyfill attr()), custom units defined in JS (vmax), custom media queries in JS, etc, custom easings in JS, etc. So this is FunCSS.
  • But I don't only want to use this technology to polyfill CSS features, but to actually program in CSS. The goal of FunCSS want to program interactive animations in a nice declarative style, with much less code than the same with JS. I don't really care visitors with disabled JS.
  • I would like FunCSS to be part of the PostCSS ecosystem somehow. One option is that FunCSS is a plugin that PostCSS users can use. The other option, which @ai just mentioned, is that FunCSS uses PostCSS as a tool -- and provide a means for the users to add other PostCSS plugins. I don't think I want FunCSS to be one language, like Less/Sass/SCSS/Stylus are, instead, to be configurable to the user's need, like PostCSS plugin packs are. And I would like the API to be similar to PostCSS. Maybe...

funcss([ cssnext() ]).process(css, ...).then(function(result) { fs.writeFileSync('app.css', result.css); fs.writeFileSync('app.js', result.js); });

But then JS output will be proprietary of FunCSS, and no other PostCSS plugins will be able to use it. Then the vmin, in-range and attr polyfills will either have their own mechanism for JS output, which is not good, or they will be funcss plugins, which is good. The other option is what we have talked about, to have a postCSS way of outputting JS, and then these polyfills need not depend on FunCSS.

@MoOx as I'm thinking about this, I start to get your point that this is dirty. Indeed, this approach (creating a plugin for js output) can encourage simple but bad solutions. Generating "good" JavaScript code (interoperable, unintrusive, reactive to changes in the DOM, optimized, well minifiable, dependencies handled) requires a lot of logic (and magic). Much of this kind of logic is and will be implemented in FunCSS, and maybe we should not provide an easy way to avoid it.

So @ai maybe I would vote for FunCSS to be the recommended tool when a postcss plugin wants to output JS, at least for now. If later it proves to be useful, you can migrate a part of the JS output handling logic to PostCSS, and make it official. But now I couldn't really tell what would be a good subset of FunCSS that might be useful for others too. Certainly not concatenating fragments together :) about which Maxime is rightly concerned.

@cie I belive that we must try different ideas. React on first state was strange idea. Every idea need time to find, analyse and fix childhood diseases. So, let’s experiment and try to find, what we will get :).

So, I agree with @ben-eb that we should not add result.js. At first state we can just replace parser inside FunCSS. It will get the first feedback for us.

ok, thx :)

I don't think that PostCSS is a bad fit for this project, in fact you should use it inside FunCSS to parse the CSS and generate JS from that. I just disagree with the methodology used to try and couple the two together, because there are better ways to do that, for example browserify/webpack middleware. Having a plugin based architecture in FunCSS seems like the best architectural decision.

But I think it's a cool project, and I've definitely wanted something like functional CSS for a while, so good luck with it. :+1:

:) Thanks!

I know this is old, but in general, I think we need a standardized way that postcss plugins can be coupled with "polyfills", where each polyfill has a standard set of way to hook into builtin browser features that are useful for polyfills (like unique class names or attributes, canvas or worklet rendering layers, automatic shadow DOM support, etc).

Please see https://github.com/postcss/postcss/issues/577#issuecomment-633666629 for more details.

I've definitely wanted something like functional CSS for a while

Speaking of that, maybe we can just abstract on top of CSS variables / custom properties. F.e., given the following CSS,

.foo {
  --some-prop: 1px;
  /* ... styles that use var(--some-prop) ... */
}

we can implement some a tool on the JS side that automatically gives you a simple abstraction:

div.classList.add(style.classes.foo)

// call this any time later to update the style's `--some-prop` variable.
style.update({someProp: '10px'})

I've been doing this to pass any dynamic information inincluding mouse positions or an element's size, etc.

Of course it doesn't solve the CSS-designer-avoiding-JS issue if the CSS designer wants to be calling those functions with values; the CSS designer can specify what inputs are needed and a JS developer would pass those in.

I still think CSS could benefit from functions that live inside the CSS only, for code organization, but we can still let the external dynamic information be passed in as CSS vars/props.

Do you have API in your mind?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

manulitopetito picture manulitopetito  Â·  12Comments

StephenEsser picture StephenEsser  Â·  9Comments

Semigradsky picture Semigradsky  Â·  3Comments

AviVahl picture AviVahl  Â·  3Comments

OEvgeny picture OEvgeny  Â·  9Comments