Skip to content

Commit

Permalink
update readme
Browse files Browse the repository at this point in the history
  • Loading branch information
nguyenyou committed Jun 5, 2024
1 parent 70db552 commit 1b8365d
Showing 1 changed file with 0 additions and 230 deletions.
230 changes: 0 additions & 230 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<details>
<summary>Approach 1</summary>
```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)
}
```
</details>

<details>
<summary>Approach 2</summary>

```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)
}
```
</details>


## Acknowledgement

This project is inspired by https://github.com/mokshit06/typewind. Thank you a lot for making the library.
Expand Down

0 comments on commit 1b8365d

Please sign in to comment.