sebastiaanvisser / clay

A CSS preprocessor as embedded Haskell.
Other
356 stars 72 forks source link

Render to key/value pairs #208

Open polux opened 4 years ago

polux commented 4 years ago

It would be great if in addition to rendering to text, clay could also "render" to something more structured. That way it could be used as a DSL for inline CSS in combination with libraries like miso for instance.

turion commented 4 years ago

That's a cool idea. What data structure would you propose clay should render to?

polux commented 4 years ago

In my particular case I'm interested in getting a Map String String that I can pass to miso's style attribute. But to make it generic enough that it's usable with other libs, I'd say any json-like structure that represents the AST of the CSS. Does that make sense?

turion commented 4 years ago

Yep, that makes a lot of sense. You mean https://hackage.haskell.org/package/miso-0.21.2.0/docs/Miso-Html.html#v:style_, right?

Clay itself basically collects a list of Rules (see http://hackage.haskell.org/package/clay-0.13.1/docs/Clay-Stylesheet.html#t:Rule). If you can write a function Rule -> Map String String, then you're halfway done.

I'm not sure this should be part of the clay package, or rather a separate clay-miso package. Either way I'm fine with adding such code to this github repo, even if it's a separate package.

turion commented 4 years ago

But to make it generic enough that it's usable with other libs

That can be a long-term goal, but let's first solve the immediate problem and connect clay to miso. The next step can be refactoring and connecting to other frameworks.

polux commented 4 years ago

Yep, that makes a lot of sense. You mean https://hackage.haskell.org/package/miso-0.21.2.0/docs/Miso-Html.html#v:style_, right?

Yes!

Clay itself basically collects a list of Rules (see http://hackage.haskell.org/package/clay-0.13.1/docs/Clay-Stylesheet.html#t:Rule). If you can write a function Rule -> Map String String, then you're halfway done.

Exactly, I had looked for that in the code of clay but it seems that such an intermediate map doesn't exist: the list of rules is converted to a Builder immediately.

I'm not sure this should be part of the clay package, or rather a separate clay-miso package. Either way I'm fine with adding such code to this github repo, even if it's a separate package.

That would be definitely simpler at first. But don't you think a function of type Rule -> Map String String would make sense in the clay package itself? Another thing I wasn't sure about is that I suppose that some rules don't make sense for an inline style (like keyframes, I don't think you can define them inline?). So that function in clay-miso would have to be partial.

turion commented 4 years ago

Clay itself basically collects a list of Rules (see http://hackage.haskell.org/package/clay-0.13.1/docs/Clay-Stylesheet.html#t:Rule). If you can write a function Rule -> Map String String, then you're halfway done.

Exactly, I had looked for that in the code of clay but it seems that such an intermediate map doesn't exist: the list of rules is converted to a Builder immediately.

No, this map doesn't exist yet, someone needs to implement it. Maybe you? :D

I'm not sure this should be part of the clay package, or rather a separate clay-miso package. Either way I'm fine with adding such code to this github repo, even if it's a separate package.

That would be definitely simpler at first. But don't you think a function of type Rule -> Map String String would make sense in the clay package itself?

It would make more sense if the resulting map is used in some way, but I can't see how the map is sufficient information to recover the whole well-readable rendering that clay produces. clay outputs human-readable CSS, but such a map is too much of a machine format, I think.

But let's start with a small step first, and implement the function. As you suggest, let's add it to Clay.Render. If we notice feature creep, we can still move it to a separate package.

Another thing I wasn't sure about is that I suppose that some rules don't make sense for an inline style (like keyframes, I don't think you can define them inline?). So that function in clay-miso would have to be partial.

Hmm, I think that's best done as a separate endofunction either on Rule oder Map String String that deletes all inapplicable rules. I wouldn't include that at first, but you're welcome to open a separate follow-up issue then.

polux commented 4 years ago

Hmm, I think that's best done as a separate endofunction either on Rule oder Map String String that deletes all inapplicable rules. I wouldn't include that at first, but you're welcome to open a separate follow-up issue then.

Actually I don't think we can do without it: other than Property I'm not sure how to translate other constructors of Rule into entries of a Map String String.

polux commented 4 years ago

Actually, the low-level constructors for attributes are public in Miso, so I guess I could define my own style attribute that takes a Text rendered by clay. It is probably way simpler than re-factoring clay's rendering to output a map.

polux commented 4 years ago

My bad, haddock makes it look like the low-level constructors are exposed but they aren't.

bsima commented 3 years ago

@polux FWIW Here is a hacky way to integration Miso and Clay that I came up with a while back, it worked well enough for a medium-sized project with lots of client-side stuff:

-- | Put 'Clay.Css' into a Miso-compatible style property.
--
-- Allows us to use any amount of CSS written with Clay inlined in HTML or
-- dynamically as JavaScript object properties. The implementation is a bit
-- hacky, but works.
css :: Clay.Css -> Attribute action
css = Miso.style_ . Map.fromList . f . Clay.renderWith Clay.htmlInline []
  where
    f :: L.Text -> [(MisoString, MisoString)]
    f t =
      L.splitOn ";" t
        <&> L.splitOn ":"
        <&> \(x : y) -> (toMisoString x, toMisoString $ L.intercalate ":" y)