noprompt / garden

Generate CSS with Clojure
1.34k stars 91 forks source link

Support for existing CSS resources #24

Open mdhaney opened 11 years ago

mdhaney commented 11 years ago

One of the killer features of enlive/enfocus (for me at least) is the ability to define snippets/templates based on existing resources. This is a huge win for the common scenario of HTML/CSS designers working independently from developers. I can get some static markup from a designer, define some selectors to extract the pieces I need to build my templates, and when he/she updates the HTML, at best I just drop in the new file, at worst I may have to tweak the selectors and then everything else falls into place.

This would be a great feature for garden to support. I see it basically working like deftemplate/defsnippet in enlive, where you can give it a filename and a list of selectors/transformations. The CSS would be parsed and return the same datastructures used when you are building the css programmatically. Once this infrastructure was in place, it would be straightforward in the future to extend it to support LESS, Stylus, SCSS, etc. by plugging in a different parser.

If someone else is working on this already, speak up and I'll help out however I can. Otherwise, I'm willing to take the lead on adding this feature, but I would like to get feedback and discuss design/implementation details with the rest of the community first.

noprompt commented 11 years ago

This is one of those ideas I think would be great to implement but am unsure if it makes sense to build it into Garden. That being said I've actually been toying around with a similar idea but strictly on the client side. The concept is to combine goog.cssom with Garden in such a way that you can query for the parts of the CSSOM you want and transform them directly (which apparently is faster and easier than just banging on an element's style property).

FWIW I did start on some code to convert CSS into Garden data structures but have been side tracked recently by some exploration client side. You should definitely investigate this!

If you're thinking of digging in to parsing LESS/Stylus you might be able to hack something together with a some ClojureScript targeting NodeJS (if you can get ahold of their parse trees). With Sass on JRuby this should definitely be possible. Then it would be a matter of interpreting/emitting Garden. It's definitely not a weekend project!

mdhaney commented 11 years ago

I started thinking after I posted that it might be better to do this as a separate project.

I think the simplest place to start is just a parser for CSS that emits Garden data structures. That would satisfy the simplest use case of being able to suck in some CSS from a designer, or even a library like Bootstrap or Zurb, and then do stuff with it like extract only the styles needed for a particular snippet/web-component, or map those styles to "generic" styles used for generated markup in my code (so the markup generation isn't tied to a particular set of class names used by any given library).

I'm pretty new to Clojure, but have a lot of DSL experience (Java mostly), so I'll probably take a stab at this parser when I get a free weekend or two. I'll let you know when I have something to look at, and we can see then if it makes sense to combine the work or keep the libraries separate. Also looking forward to see what you do with Garden and CSSOM - some interesting possibilities there.

mdhaney commented 11 years ago

Hmmm, trying to think of a good project name for the parser part, maybe keep with the gardening theme.

Of course "rake" is right out. Would "hoe" be too over the top?

noprompt commented 11 years ago

The one I was working on was called "tiller," as in roto-tiller. Hah!

Joel

On Oct 18, 2013, at 21:18, Mike Haney notifications@github.com wrote:

Hmmm, trying to think of a good project name for the parser part, maybe keep with the gardening theme.

Of course "rake" is right out. Would "hoe" be too over the top?

— Reply to this email directly or view it on GitHub.

ToBeReplaced commented 11 years ago

As a smaller request -- being able to include raw CSS strings as rules would be useful for people with existing CSS to work with. A common case would be a browser-reset.

mdhaney commented 11 years ago

@ToBeReplaced - yep, that's along the same lines of what I'm talking about. Basically, we need a parser that takes the CSS text and returns a garden-style data structure representing it. The input source for that text would be irrelevant - you could pass it a hard-coded string, or slurp in a file.

I'm going to try to clear the deck to work on this soon, hopefully this coming weekend. It's becoming more critical for something I'm trying to do, and it looks like I'm not the only one that needs this feature.

I've been looking at different parser libraries and Instaparse looks promising, so I think I'll go with that unless anyone has a better recommendation.

noprompt commented 11 years ago

As a smaller request -- being able to include raw CSS strings as rules would be useful for people with existing CSS to work with. A common case would be a browser-reset.

@ToBeReplaced This has been requested before (not here). To be honest, I really don't like the idea but, hey, it's not all about me. The reason I don't like it is it means I would need to add another type or some other sort of special handling to Garden. However it is possible and I could add a raw function that would permit this.

Another reason why I have been reluctant to add support for raw CSS strings is because it creates and ambiguity in the the context of a rule. How should I know that this

[:h1 "font-weight: bold" {:font-family 'fugly}]

should not be interpreted as

h1, font-weight: bold {
  font-family: fugly;
}

when clearly we want to be able to say

[input "[type='text']" ...]

at the same time.

You might think that, well, we could just use a vector for the selector! But that actually get's us in the same sort of trouble. Long story short, while I'm not enamored with this idea it's clear that people may have a need for it. I just need time to ruminate on how that should look.

@txmikester Instaparse was what I was using. The grammar I wrote was probably bad because the resulting parser was very slow. :laughing: Hopefully you can do better!

mdhaney commented 10 years ago

Sorry for the delay. I started trying to do this a couple months ago, became frustrated with the sorry state of the CSS "spec" and trying to create a reasonable grammar from it, then I got busy and am just now getting time to look at this.

My specific use case right now is I want to suck in CSS files (specifically, Bootstrap 3) and manipulate the styles programmatically, e.g. extract just the relevant styles for the components I am building and then generate an optimized style-sheet (either statically or on the fly). Because of the issues I ran into before with trying to find a good CSS 3 grammar, I decided to look at LESS and found less4j, a java library that implements a native parser using ANTLR (as opposed to some other libs that simply run less.js on Rhino, which seemed a bit heavy for my needs).

So far, I've got the parser hooked up and am walking the AST and have it cleanly mapped to a protocol that I just need to implement for each node type. Now I need to start seriously thinking about how to structure the output, and that's where I could use some feedback. Building a simple map of selectors -> rules would be pretty straightforward, but then we would lose the flexibility of having variables and mix-ins. I think we can do better than that.

I have a few ideas in mind, but nothing too concrete as I literally just started working on this today. You said you were working on something similar earlier, so if you have given some thought to the best way to structure the parsed data, I would love to hear your ideas.

jeluard commented 10 years ago

Very neat idea!

Have you considered the hickory format? hickory is essentially the same idea but for hiccup. It's a format slightly different from hiccup but optimized for machine transformation. You can see what can be achieved with selectors. I think it fits your requirement and could allow for advanced filtering based on css selector. It could also allow to transform rules, a specific example I have in mind is helping with ShadowDOM namespacing with hat and cat combinators (e.g. transforming bootstrap .button into my-component ^^ .button) Finally hickory format allows easy transformation from and to hiccup which is a big win.

If the whole thing could work with ClojureScript that would be a big win. Or at least make sure it is extensible enough so that it could be done. Probably the instaparse road was a better fit here. Would you still consider it?

jeluard commented 10 years ago

Actually a you might have different strategies to parse (say on a browser you could use browser features) it would be better to have something pluggable.

mdhaney commented 10 years ago

@jeluard - Thanks for the comments. I've heard of Hickory, but never really looked into it before. I'll have to take a look and see how it might fit.

This is one of those things that I know I need, and probably others too, but every time I start down the road, something more pressing comes up. Then a month later, I find myself really wanting this ability again, etc. So yeah, it's probably time to just do something about it already.

So conceptually, it's very simple. We should be able to parse styles in various formats into some intermediary format, and garden seems to be an obvious candidate for that format. Really, that's the heart of it, and once you have these styles as Clojure(script) data structures, you can do anything with them.

To start with, let's take the simplest input format we can deal with, which is CSS itself. So, there are a couple of other things you have handle, like importing other stylesheets, but in essence it's just a sequence of selectors->styles. It's easy to picture a data format for this - just a map with selectors as keys and maps for the styles. Done. From there, we can easily transform to garden or whatever format we need.

Considering other formats starts to bring up questions I'm having a hard time answering. How do we handle things like mix-ins, for instance? If we try to get too clever, I'm afraid we will end up essentially re-implementing all of SASS/LESS/Stylus, which is most likely a waste of time. So maybe we consider adding a few pieces to the intermediate data format, to represent concepts such as variables and mix-ins, and then just let whoever is consuming the data decide how to deal with it? But if we do that, is it really worth supporting those other formats at all? Restated - is there any value in handling SASS et. al. syntactically while ignoring the semantics? I'm becoming more skeptical of this the more I think about it.

But there is definitely value in handling just plain CSS, so that's probably what I need to focus on first. I think the key first step is to just to be able to get CSS files into something useable, so that's what I'm going to focus on. And yes, that means writing a real parser with Instaparse. It's probably going to take a bit of trial and error, because the grammar shown in the spec has a lot of issues. But as a goal, I would think if we could parse say, Bootstrap and Foundation successfully, then we've probably hit most if not all of what we need to support.

I really appreciate the comments/insight. Keep them coming!

jeluard commented 10 years ago

There's definitively great value in handling CSS alone. Others languages could then be supported later on, as needed. They compile down to CSS anyway so that's probably not a blocker.

I definitively think a hickory style makes sense if you want to analyze/transform, now as hickory can be converted to hiccup and vice versa it's not clear which should be the pivot. Maybe @davidsantiago can give some lights here.

What I think would be of great value is the ability to then transform the resulting data. You could think of merging several CSS files and removing dupes, detecting overridden rules. Or just import the button class from bootstrap plus all its dependencies and throw all the rest. Now that would require some logic to understand relations between selectors which is probably not trivial.

jeluard commented 10 years ago

Maybe this node library can give some inspiration.

noprompt commented 10 years ago

@mdhaney @jeluard I have started work on this here. Full support for CSS should be available within the next couple days.

jeluard commented 10 years ago

@noprompt cool stuff! How do you intend to parse CSS?

noprompt commented 10 years ago

@jeluard Sass parses it. :smile: Thorn just uses a custom visitor that which emits data instead of compiled CSS. The data is then converted to EDN at the JRuby/Clojure boundary and the rest is pretty strait forward.

noprompt commented 10 years ago

@jeluard @mdhaney Here's an example of the output: https://gist.github.com/noprompt/9414584.

kylecordes commented 10 years ago

I would find something like what is discussed here, very helpful. I would like to be able to take existing CSS, auto convert it to Garden/clj, and work with it from there.

noprompt commented 10 years ago

@kylecordes I'm in agreement. I just haven't had the time to finish fleshing out what that would look yet. I've done a bit of prototyping around the subject but nothing I'm ready to commit at this point.

rosejn commented 8 years ago

Hi, I just found this thread, and I've been working on adding this support for Garden to import CSS. I've successfully converted some large files like animate.css and material.css (from material design light). It's implemented here at the moment:

https://github.com/thinktopic/greenhouse/blob/master/src/greenhouse/css.clj

I'm happy to contribute if appropriate, or just build it out as a separate tool if you don't want it to be in Garden. So far it parses CSS files, including media query and keyframe rules, and it can return a full Garden data structure. It can also import classes from a CSS file, and then make them available as mixins. I'm still trying to figure out what's the best approach in terms of how to incorporate a large CSS file into a project though. For example camelCase to kebab-case, defining just a big Garden blog or generating functions for all the classes, etc... It would be great to have some additional thoughts and ideas around this.

noprompt commented 8 years ago

@rosejn This is something I'm definitely interested and I'd like to discuss it further in the very near future. In a few days code will land in the 2.0.0 branch that changes the way Garden fundamentally works "under the hood". In short it will have very clear steps for parsing, desugaring, and compiling. Each step will be extensible and both consume and produce an AST with the exception of the parsing step which will only produce an AST. This will all be document in the Changelog at the time it's committed.

What this means is that we will finally have clear lines in the architecture and platform for discussing and implementing a way to introduce this feature. Support for consuming and manipulating raw CSS at every level (i.e. even nested in Garden syntax) is very important and I want to make sure we do a good job supporting the feature.

Once I've pushed up the new architecture I will notify you and we can continue the discussion then!

beders commented 6 years ago

Time to revive this issue ;) Just stumbling into the mysterious garden that is garden. Thing is: There's SCSS, and taking something like Bulma and customizing is means bringing in a gigaton of npm libraries etc.etc.

Instead, I'd love to just (load-scss "bulma/forms/...bblabl.scss) Well, one can dream. There's a native C lib that's doing a good job parsing SCSS, but with that being native, it's also a hassle to set up.

There's also an ANTLR4 gammar that can parse scss, which could be starting point: https://github.com/antlr/grammars-v4/tree/master/scss

How do you guys use CSS libraries with garden?

noprompt commented 6 years ago

@beders Parse CSS into Clojure data and rewrite into Garden (V2) AST would be the dream. I've been pretty busy with family, work, and another project to get involved in Garden so the dream is a bit stalled on my end for the time.