Skip to content

Latest commit

 

History

24 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

#+title: Winnow
#+author: James Conroy-Finn
#+startup: content

[[https://github.com/jcf/winnow/actions][https://github.com/jcf/winnow/actions/workflows/ci.yml/badge.svg]]
[[https://clojars.org/dev.jcf/winnow][https://img.shields.io/clojars/v/dev.jcf/winnow.svg]]

Tailwind CSS class merging for Clojure, ClojureScript, and Babashka.

* What It Does
Winnow resolves conflicting Tailwind classes by keeping the last value for each
utility group. We process classes left to right; later classes override earlier
ones in the same group.

#+begin_src clojure
(winnow/resolve ["px-2 py-4" "px-6"])
;; => "py-4 px-6"
;;     ↑     ↑
;;     │     └─ px-6 overrides px-2 (same group: padding-x)
;;     └─────── py-4 preserved (different group: padding-y)
#+end_src

Modifiers create separate groups. =hover:p-4= and =p-4= don't conflict:

#+begin_src clojure
(winnow/resolve ["p-2 hover:p-2" "p-4"])
;; => "hover:p-2 p-4"
#+end_src

Unknown classes pass through unchanged:

#+begin_src clojure
(winnow/resolve ["my-thing" "block" "hidden"])
;; => "my-thing hidden"
#+end_src

* Principles
- *Predictable* — Deterministic output. Same input always produces same result.
- *Strict* — Requires explicit configuration. Won't guess that =bg-brand= is a
  color unless you tell it.
- *Stable* — API designed for production. Breaking changes are versioned.
- *Tested* — 340 conformance tests, generative property tests, clojure.spec
  validation.
- *Fast* — Sub-microsecond for typical inputs. Benchmarked with Criterium.
- *Pure Clojure* — No JavaScript runtime. No external dependencies.

* Installation
#+begin_src clojure
dev.jcf/winnow {:git/url "https://github.com/jcf/winnow"
                :git/sha "LATEST"}
#+end_src

* API
#+begin_src clojure
(require '[winnow.api :as winnow])
#+end_src

** =resolve=
#+begin_src clojure
(winnow/resolve ["p-4" "p-[10px]"])            ;; => "p-[10px]"
(winnow/resolve ["bg-red-500" "bg-(--x)"])     ;; => "bg-(--x)"
(winnow/resolve ["pt-2 pr-2 pb-2 pl-2" "p-4"]) ;; => "p-4"
#+end_src

Requires a vector. Use =normalize= for flexible input.

** =normalize=
#+begin_src clojure
(def tw (comp winnow/resolve winnow/normalize))

(tw nil)                        ;; => ""
(tw "p-4 m-2")                  ;; => "p-4 m-2"
(tw ["base" nil "override"])    ;; => "base override"
(tw [["a"] ["b" "c"]])          ;; => "a b c"
#+end_src

** =make-resolver=
#+begin_src clojure
;; Custom colors
(def resolve (winnow/make-resolver {:colors #{"primary" "surface"}}))
(resolve ["bg-red-500" "bg-primary"]) ;; => "bg-primary"

;; Class prefix
(def resolve (winnow/make-resolver {:prefix "tw-"}))
(resolve ["tw-px-2 tw-px-4"]) ;; => "tw-px-4"
(resolve ["px-2 px-4"])       ;; => "px-2 px-4" (no prefix, passes through)
#+end_src

* Conformance
340 test cases derived from [[https://github.com/dcastil/tailwind-merge][tailwind-merge]].

| Library            | Tailwind | Conformance |
|--------------------+----------+-------------|
| winnow             | v4.x     |     340/340 |
| tailwind-merge-clj | v3.4     |     218/340 |

* Performance
Apple M4, =just bench=:

| Scenario     | Classes |    Time |
|--------------+---------+---------|
| Small        |       2 |  945 ns |
| Medium       |      10 | 5.49 µs |
| Large        |      25 | 12.5 µs |

* Coverage
666 patterns. Tailwind 4.x. See [[file:doc/supported-classes.org][supported-classes.org]].

Automated upstream detection runs daily via GitHub Actions to detect new
utilities added to Tailwind CSS.

* Platforms
| Platform      | Status |
|---------------+--------|
| Clojure (JVM) | ✓      |
| ClojureScript | ✓      |
| Babashka      | ✓      |

* Development
#+begin_src sh
just               # Full test suite (required before commits)
just bench         # Run benchmarks
just docs          # Regenerate supported-classes.org
just upstream-check  # Check for upstream Tailwind changes
just upstream-update # Update baseline from Tailwind CSS
#+end_src

* License
AGPL-3.0. See [[file:LICENSE][LICENSE]].

* Support
Winnow is maintained by [[https://jamesconroyfinn.com][James Conroy-Finn]]. If you find it useful, consider
[[https://github.com/sponsors/jcf][sponsoring my work on GitHub]].

For commercial licensing or consulting inquiries, email [[mailto:james@invetica.co.uk][james@invetica.co.uk]].

About

Tailwind CSS class merging for Clojure.

Topics

Resources

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages