diff --git a/README.md b/README.md index 7ac6f8d..7c8e165 100644 --- a/README.md +++ b/README.md @@ -305,236 +305,6 @@ module.exports = { }; ``` -## How it works? - -The Scalawind CLI reads your `tailwind.config.js` and using some utilities from the `tailwind` package to parse the config into a list of unititly classes. After that, we use a handlebar template to generate the actual scala code that you will use in your scala project. - -This is the handlebar template: - -```hbs -package {{package}} - -import scala.quoted.* -import scala.annotation.unused - -case class Tailwind() { - {{#each modifiers}} - def {{this.name}}(@unused styles: Tailwind): Tailwind = Tailwind() - {{/each}} - def important(@unused styles: Tailwind): Tailwind = Tailwind() - def i(@unused styles: Tailwind): Tailwind = Tailwind() -} - -object tw { - def apply(): Tailwind = Tailwind() - - {{#each standard}} - def {{this.prop}}: Tailwind = Tailwind() - {{/each}} - - {{#each modifiers}} - def {{this.name}}(@unused styles: Tailwind): Tailwind = Tailwind() - {{/each}} - def important(@unused styles: Tailwind): Tailwind = Tailwind() - def i(@unused styles: Tailwind): Tailwind = Tailwind() -} - -extension (tailwind: Tailwind) - {{#each standard}} - def {{this.prop}}: Tailwind = Tailwind() - {{/each}} - -inline def sw(inline tailwind: Tailwind): String = - ${ swImpl('tailwind) } - -def swImpl(twStyleExpr: Expr[Tailwind])(using Quotes): Expr[String] = { - import quotes.reflect.* - - def extractClassNames(term: Term): List[String] = term match { - {{#each modifiers}} - case Apply(Select(inner, "{{this.name}}"), List(styles)) => - val classes = extractClassNames(styles).map(clx => s"{{this.value}}:$clx") - extractClassNames(inner) ++ classes - {{/each}} - case Apply(Select(inner, "important"), List(styles)) => - val classes = extractClassNames(styles).map(clx => s"!$clx") - extractClassNames(inner) ++ classes - case Apply(Select(inner, "i"), List(styles)) => - val classes = extractClassNames(styles).map(clx => s"!$clx") - extractClassNames(inner) ++ classes - case Apply(Ident(name), List(inner)) => - extractClassNames(inner) :+ name.replace("_", "-") - case Inlined(_, _, inner) => - extractClassNames(inner) - case Select(inner, name) => - extractClassNames(inner) :+ name.replace("_", "-") - case Ident("tailwind") => - Nil - case Ident("tw") => - Nil - case _ => - report.errorAndAbort(s"Unexpected term: $term") - } - - val term = twStyleExpr.asTerm - val classNames = extractClassNames(term) - val combinedClasses = classNames.mkString(" ") - report.info(s"Compiled: $combinedClasses") - Expr(combinedClasses) -} -``` - -Now, let's take a look at this minimal `scalawind.scala` file that you can copy and paste into your source code. - -```scala -package scalawind - -import scala.quoted.* -import scala.annotation.unused - -case class Tailwind() { - def hover(@unused styles: Tailwind): Tailwind = Tailwind() -} - -object tw { - def apply(): Tailwind = Tailwind() - - def text_blue_500: Tailwind = Tailwind() - def text_red_500: Tailwind = Tailwind() - def hover(@unused styles: Tailwind): Tailwind = Tailwind() -} - -extension (tailwind: Tailwind) - def text_blue_500: Tailwind = Tailwind() - def text_red_500: Tailwind = Tailwind() - -inline def sw(inline tailwind: Tailwind): String = - ${ swImpl('tailwind) } - -def swImpl(twStyleExpr: Expr[Tailwind])(using Quotes): Expr[String] = { - import quotes.reflect.* - - def extractClassNames(term: Term): List[String] = term match { - case Apply(Select(inner, "hover"), List(styles)) => - val classes = extractClassNames(styles).map(clx => s"hover:$clx") - extractClassNames(inner) ++ classes - case Apply(Ident(name), List(inner)) => - extractClassNames(inner) :+ name.replace("_", "-") - case Inlined(_, _, inner) => - extractClassNames(inner) - case Select(inner, name) => - extractClassNames(inner) :+ name.replace("_", "-") - case Ident("tailwind") => - Nil - case Ident("tw") => - Nil - case _ => - report.errorAndAbort(s"Unexpected term: $term") - } - - val term = twStyleExpr.asTerm - val classNames = extractClassNames(term) - val combinedClasses = classNames.mkString(" ") - report.info(s"Compiled: $combinedClasses") - Expr(combinedClasses) -} -``` - -Then use it: - -```scala -val styles: String = sw(tw.text_red_500.hover(tw.text_blue_500)) - -// ↓ ↓ ↓ ↓ ↓ ↓ - -val styles: String = "text-red-500 hover:text-blue-500" -``` - -As you can see, the whole scalawind thing includes two parts: -- The `case class Tailwind`, the `object tw` and the `extension (tailwind: Tailwind):` is just there for the Fluent Syntax. -- The `sw` and `swImpl` is the macro that will compile all those fluent thing into a string - -### Alternative Implementation - -
- Approach 1 - - ```scala - inline def sw(inline tailwind: Tailwind): String = - ${ swImpl('tailwind) } - - def swImpl(tailwindExpr: Expr[Tailwind])(using Quotes): Expr[String] = { - import quotes.reflect.* - - def extractClassNames(term: Term): List[String] = { - var stack = List(term) - var classNames = List.empty[String] - - while (stack.nonEmpty) { - stack.head match { - case Inlined(_, _, inner) => - stack = inner :: stack.tail - case Select(inner, name) => - classNames = name.replace("_", "-") :: classNames - stack = inner :: stack.tail - case Ident("tw") => - stack = stack.tail - case unexpectedTerm => - report.errorAndAbort(s"Unexpected term: $unexpectedTerm") - } - } - classNames.reverse - } - - val term = tailwindExpr.asTerm - val classNames = extractClassNames(term) - val combinedClasses = classNames.reverse.mkString(" ") - report.info(s"$term") - Expr(combinedClasses) - } - ``` -
- -
-Approach 2 - - ```scala - def swImpl(twStyleExpr: Expr[Tailwind])(using Quotes): Expr[String] = { - import quotes.reflect.* - - def extractClassNames(term: Term): List[String] = { - var currentTerm: Option[Term] = Some(term) - var classNames = List.empty[String] - - while (currentTerm.isDefined) { - currentTerm match { - case Some(Inlined(_, _, inner)) => - currentTerm = Some(inner) - case Some(Select(inner, name)) => - classNames = classNames :+ name.replace("_", "-") - currentTerm = Some(inner) - case Some(Ident("tw")) => - currentTerm = None - case Some(unexpectedTerm) => - report.errorAndAbort(s"Unexpected term: $unexpectedTerm") - case None => - // This should never happen - report.errorAndAbort("Unexpected None in currentTerm") - } - } - classNames.reverse - } - - val term = twStyleExpr.asTerm - val classNames = extractClassNames(term) - val combinedClasses = classNames.mkString(" ") - report.info(s"Extracted term: $term, combined classes: $combinedClasses") - Expr(combinedClasses) - } - ``` -
- - ## Acknowledgement This project is inspired by https://github.com/mokshit06/typewind. Thank you a lot for making the library.