Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compile-time CSS generation #88

Open
japgolly opened this issue Jun 17, 2016 · 9 comments
Open

Compile-time CSS generation #88

japgolly opened this issue Jun 17, 2016 · 9 comments

Comments

@japgolly
Copy link
Owner

It would be fantastic to generate CSS at compile-time instead of runtime, mainly because it would reduce the output JS size to near zero, making #72 obsolete.

Example:

val abc = style(display.block)
// Macro turns ↑ into something like ↓
val abc = StyleA(…, PreGeneratedCss("display:block"))

It would have to handle autoprefixing too.

How/could it determine whether variables are static/dynamic to handle marginLeft(zzz.px * 2)?

Even if it just takes care of the easy, static cases, I wonder how large the JS size savings could be...

@edmundnoble
Copy link

Should pre-generated CSS be generated using dev- or prod- settings? How could one adjust the settings used at compile-time?

@japgolly
Copy link
Owner Author

japgolly commented Oct 7, 2016

When compiling for Scala.JS it's easy because there is a flag known at compile-time which specifies whether it's compiling for dev (on when fastOptJS) or prod (on when fullOptJS).

For JVM compilation there'd have to be a flag that you add to scalaOptions in SBT to indicate (or make inferable) dev vs prod. Normally for this case I use -Xelide-below OFF for prod mode which elides assertions and other debug-type checks.

@japgolly
Copy link
Owner Author

This idea's been in my head for a while now:

It would be great to support a macro that just accepts plain CSS as a string, parses it at compile-time, validates it, depending on macro args adds browser prefixes, saves it as a String (thus allowing 99% of this library to disappear at runtime), and then creates a StyleA as normal.

@Grogs
Copy link

Grogs commented Jan 17, 2018

There's a few libraries with CSS parsers out there that could be used.

I suppose the question is how easy would be be to take one of their ASTs and generate a StyleA.

Rapture HTML by Jon Pretty has one based on CSSParser: https://github.com/propensive/rapture/tree/dev/css/shared/src/main/scala/rapture/css

CssParse (a subproject of FastParse) by Li Haoyi: https://github.com/lihaoyi/fastparse/tree/master/cssparse/shared/src/main/scala/cssparse

@japgolly
Copy link
Owner Author

Ok, so it seems

  • easy to have a macro parse and transform a string literal as CSS at compile-time (eg. parse("color:red"))
  • easy to have a css string interpolator at runtime (eg. css"margin: $m px")
  • impossible to combine both (eg. css"margin: $m px" which expands $m, parses and transforms CSS at compile-time)

@ddworak
Copy link

ddworak commented Apr 28, 2020

We use scalacss with compile-time / backend CSS rendering quite extensively in udash-css, while reusing most of the DSL. The output JS is just the classnames (safe to use though!). You can check https://guide.udash.io/frontend/templates or dive into the code https://github.com/UdashFramework/udash-core/tree/master/css, https://github.com/UdashFramework/udash-core/blob/master/macros/src/main/scala/io/udash/css/macros/StyleMacros.scala

@japgolly
Copy link
Owner Author

japgolly commented Apr 28, 2020

Oh very cool, thanks! How do you handle supporting both dev and prod modes? (Specifically, controlling whether the macros generate verbose or minimised CSS?)

Also, a few weeks ago I did a prototype for scalacss v2 and it worked 🎉 I'm envisioning a future where you just paste normal CSS and it calls out to the JS ecosystem at compile-time to do things like validation, minification, auto-prefixing, etc. The idea of a Scala-based DSL for CSS has turned out to be a failure in my opinion.

  1. It's too hard to type all possibilities for ~10% of CSS attributes.
  2. the CSS world moves too fast and it's not feasible to try to keep up.
  3. Personally I prototype my HTML & CSS using just HAML with embedded CSS and then once the prototypes are done, I have to copy the styles from CSS which the whole world understands to a special DSL which only Scala understands. My experience here might not be indicative of the whole ecosystem but it's just become the most efficient workflow for me.

I'm envisioning something like this:

    val header =
      css"""color: #3659e2;
           |margin-bottom: $size;
           |""".stripMargin

    val betterHeader =
      css"""$header
           |font-weight: bold;
           |""".stripMargin

It would still need some custom stuff to be able to support things like addClassNames but should be quite a small surface area.

What do you think?

@japgolly
Copy link
Owner Author

japgolly commented Apr 28, 2020

If you like my vision or have a similar one we could create it together, or if your vision is different we could create a shared base for both scalacss & udash if it makes sense.

@ddworak
Copy link

ddworak commented Apr 28, 2020

The dev and prod modes are pretty simple - if it's compile-time generation, sbt calls a runnable class which generates a CSS stylesheet like:

(backend / Compile / runMain).toTask(s" io.app.backend.css.CssRenderer $path false")

where false signifies whether that's prod or dev and can be set in SBT via dependencies from fastOpt and fullOpt respectively. Sometimes we even generate the stylesheets on startup and keep them in JVM memory - backend apps usually have some knowledge about the environment or a config flag anyway.

My experience with CSS in SJS applications (quite a few production cases, including one large enterprise app) is that the less is done on the frontend, the better, especially since the JS size is usually already an issue with Scala.js. We've managed to keep all of our styles as udash-css styles, which means they're pretty strict - and maybe it's for the best. Even complex cases like theming can be handled on the backend if you don't just render one stylesheet at compile time, but defer that to run time. There are some corner cases (I'd say < 5%) where we just had to override the style manually or copied some CSS, and I think in those cases your v2 would fit very nicely. Your approach with compile-time validation etc. may also work on the backend, given that Node.js / jsdom can handle these use-cases.

I'd be happy to take a look at the prototype next week or as soon as you can share. I'll discuss with the team and we can come up with some shared part for sure!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants