Skip to content

Commit

Permalink
feat: First working version
Browse files Browse the repository at this point in the history
  • Loading branch information
Oudwins committed Mar 10, 2024
1 parent 7358c1d commit 790b7e5
Show file tree
Hide file tree
Showing 5 changed files with 405 additions and 0 deletions.
73 changes: 73 additions & 0 deletions pkg/twmerge/class-utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package twmerge

import (
"regexp"
"strings"
)

type GetClassGroupIdfn func(string) (isTwClass bool, groupId string)

func MakeGetClassGroupId(conf *TwMergeConfig) GetClassGroupIdfn {
var getClassGroupIdRecursive func(classParts []string, i int, classMap *ClassPart) (isTwClass bool, groupId string)
getClassGroupIdRecursive = func(classParts []string, i int, classMap *ClassPart) (isTwClass bool, groupId string) {
if i >= len(classParts) {
if classMap.ClassGroupId != "" {
return true, classMap.ClassGroupId
}

return false, ""
}

if classMap.NextPart != nil {
nextClassMap := classMap.NextPart[classParts[i]]
isTw, id := getClassGroupIdRecursive(classParts, i+1, &nextClassMap)
if isTw {
return isTw, id
}
}

if classMap.Validators != nil && len(classMap.Validators) > 0 {
remainingClass := strings.Join(classParts[i:], string(conf.ClassSeparator))

for _, validator := range classMap.Validators {
if validator.Fn(remainingClass) {
return true, validator.ClassGroupId
}
}

}
return false, ""
}

var arbitraryPropertyRegex = regexp.MustCompile(`^\[(.+)\]$`)

getGroupIdForArbitraryProperty := func(class string) (bool, string) {
if arbitraryPropertyRegex.MatchString(class) {
arbitraryPropertyClassName := arbitraryPropertyRegex.FindStringSubmatch(class)[1]
property := arbitraryPropertyClassName[:strings.Index(arbitraryPropertyClassName, ":")]

if property != "" {
// I use two dots here because one dot is used as prefix for class groups in plugins
return true, "arbitrary.." + property
}
}

return false, ""
}

return func(baseClass string) (isTwClass bool, groupdId string) {

classParts := strings.Split(baseClass, string(conf.ClassSeparator))
// remove first element if empty for things like -px-4
if len(classParts) > 0 && classParts[0] == "" {
classParts = classParts[1:]
}
isTwClass, groupId := getClassGroupIdRecursive(classParts, 0, &conf.ClassGroups)
if isTwClass {
return isTwClass, groupId
}

return getGroupIdForArbitraryProperty(baseClass)
}

}
53 changes: 53 additions & 0 deletions pkg/twmerge/create-tailwind-merge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package twmerge

import (
"strings"

lru "github.com/Oudwins/tailwind-merge-go/pkg/cache"
)

// create the config (just gets the config passed in)

// create the config utils
// LRU cache
// split modifiers
// -> for things like hover:bg-x
// class utils
// -> for splitting classes

// cache get & set

// merge fn
// 1. check cache
// 2. mergeClassList
// 3. set cache

// should this also take a cache directly?
func CreateTwMerge(config *TwMergeConfig, cache lru.Cache) func(args ...string) string {
if config == nil {
config = MakeDefaultConfig()
}
if cache == nil {
cache = lru.Make(config.MaxCacheSize)
}

splitModifiers := MakeSplitModifiers(config)

getClassGroupId := MakeGetClassGroupId(config)

mergeClassList := MakeMergeClassList(config, splitModifiers, getClassGroupId)

return func(args ...string) string {
classList := strings.Join(args, " ")
cached := cache.Get(classList)
if cached != "" {
return cached
}
// check if in cache
merged := mergeClassList(classList)
cache.Set(classList, merged)
return merged
}
}

var Merge = CreateTwMerge(nil, nil)
88 changes: 88 additions & 0 deletions pkg/twmerge/merge-classlist.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package twmerge

import (
"regexp"
"slices"
"strings"
)

const SPLIT_CLASSES_REGEX = `\s+`

var splitPattern = regexp.MustCompile(SPLIT_CLASSES_REGEX)

func MakeMergeClassList(conf *TwMergeConfig, splitModifiers SplitModifiersFn, getClassGroupId GetClassGroupIdfn) func(classList string) string {
return func(classList string) string {
classes := splitPattern.Split(strings.TrimSpace(classList), -1)
unqClasses := make(map[string]string, len(classes))
resultClassList := ""

for _, class := range classes {
baseClass, modifiers, hasImportant, maybePostfixModPosition := splitModifiers(class)

// there is a postfix modifier -> text-lg/8
if maybePostfixModPosition != -1 {
baseClass = baseClass[:maybePostfixModPosition]
}
isTwClass, groupId := getClassGroupId(baseClass)
if !isTwClass {
resultClassList += class + " "
continue
}
// we have to sort the modifiers bc hover:focus:bg-red-500 == focus:hover:bg-red-500
modifiers = SortModifiers(modifiers)
if hasImportant {
modifiers = append(modifiers, "!")
}
unqClasses[groupId+strings.Join(modifiers, string(conf.ModifierSeparator))] = class

conflicts := conf.ConflictingClassGroups[groupId]
if conflicts == nil {
continue
}
for _, conflict := range conflicts {
// erase the conflicts with the same modifiers
unqClasses[conflict+strings.Join(modifiers, string(conf.ModifierSeparator))] = ""
}
}

for _, class := range unqClasses {
if class == "" {
continue
}
resultClassList += class + " "
}
return strings.TrimSpace(resultClassList)
}

}

/**
* Sorts modifiers according to following schema:
* - Predefined modifiers are sorted alphabetically
* - When an arbitrary variant appears, it must be preserved which modifiers are before and after it
*/
func SortModifiers(modifiers []string) []string {
if modifiers == nil || len(modifiers) < 2 {
return modifiers
}

unsortedModifiers := []string{}
sorted := make([]string, len(modifiers))

for _, modifier := range modifiers {
isArbitraryVariant := modifier[0] == '['
if isArbitraryVariant {
slices.Sort(unsortedModifiers)
sorted = append(sorted, unsortedModifiers...)
sorted = append(sorted, modifier)
unsortedModifiers = []string{}
continue
}
unsortedModifiers = append(unsortedModifiers, modifier)
}

slices.Sort(unsortedModifiers)
sorted = append(sorted, unsortedModifiers...)

return sorted
}
52 changes: 52 additions & 0 deletions pkg/twmerge/modifier-utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package twmerge

type SplitModifiersFn = func(string) (baseClass string, modifiers []string, hasImportant bool, maybePostfixModPosition int)

func MakeSplitModifiers(conf *TwMergeConfig) SplitModifiersFn {
separator := conf.ModifierSeparator

return func(className string) (string, []string, bool, int) {
modifiers := []string{}
modifierStart := 0
bracketDepth := 0
// used for bg-red-500/50 (50% opacity)
maybePostfixModPosition := -1

for i := 0; i < len(className); i++ {
char := rune(className[i])

if char == '[' {
bracketDepth++
continue
}
if char == ']' {
bracketDepth--
continue
}

if bracketDepth == 0 {
if char == separator {
modifiers = append(modifiers, className[modifierStart:i])
modifierStart = i + 1
continue
}

if char == conf.PostfixModifier {
maybePostfixModPosition = i
}
}
}

baseClassWithImportant := className[modifierStart:]
hasImportant := baseClassWithImportant[0] == byte(conf.ImportantModifier)
var baseClass string
if hasImportant {
baseClass = baseClassWithImportant[1:]
} else {
baseClass = baseClassWithImportant
}

return baseClass, modifiers, hasImportant, maybePostfixModPosition

}
}
Loading

0 comments on commit 790b7e5

Please sign in to comment.