Folders and files
| Name | Name | 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]].