I was working on FunCSS for the last 6 months, which is a project with similar aims to PostCSS:
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?
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:
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?
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):
result.messages to and put there { type: 'js', content: [string], map: [SourceMapGenerator] }.gulp-postcss, etc plugins to support JS messages from result.messages.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:
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.
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:
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
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.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
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:
: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.
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?
Most helpful comment
@MoOx
React Style is solveing right problems. But it solves it in wrong way. We should keep looking better solution.