Skip to content

Commit

Permalink
Document :require-macros usage pattern
Browse files Browse the repository at this point in the history
  • Loading branch information
plexus committed Sep 4, 2023
1 parent 4b9b6cf commit b1e21c1
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 4 deletions.
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,56 @@ components in the style rules section, even in ClojureScript, see the section
[:.foo referenced]) ;; use as style rule
```

#### Computing values (referencing vars) inside style rules in cljc files

Style rules are processed during macroexpansion, which happens in Clojure, even
when compiling ClojureScript. This means that any code inside style rules needs
to be able to evaluate in Clojure by the time ClojureScript starts compiling the
`defstyled` form.

Consider this namespace

```clj
;; components.cljc
(ns my.components
(:require [lambdaisland.ornament :as o]))

(def my-tokens {:main-color "green"})

(o/defstyled with-code :div
{:background-color (-> my-tokens :main-color)})
```

In Clojure this works as you would expect, but when compiling this as a
ClojureScript file it fails, because this file was never loaded as a Clojure
namespace, so the var `#'my.components/my-tokens` doesn't exist.

To fix this you can use `:require-macros`. This instructs the ClojureScript
compiler to load a given Clojure namespace before continuing the compilation of
the current namespace.

```clj
(ns my.components
(:require [lambdaisland.ornament :as o])
#?(:cljs (:require-macros my.components)))

#?(:clj
(def my-tokens {:main-color "green"}))

(o/defstyled with-code :div
{:background-color (-> my-tokens :main-color)})
```

This way before ClojureScript compiles `my.components` as a cljs file, it first
loads `my.components` as a clj namespace. This creates the `#'my-tokens` var. It
the continues with the cljs compilation, so that when it gets to `defstyled` and
processed the rules (`{:background-color ...}`) `my-tokens` resolves to the
correct var, and can be evaluated.

Wrapping `my-tokens` in `#?(:clj ...)` is not strictly necessary, but it helps
to emphasize the point that this definition is only ever used on the Clojure
side, you don't need it in your compiled ClojureScript.

#### Shadow-cljs build hook example

This is enough to get recompilation of your styles to CSS, which shadow-cljs
Expand Down
17 changes: 13 additions & 4 deletions test/lambdaisland/ornament_test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
(:require [lambdaisland.ornament :as o]
[clojure.test :refer [deftest testing is are use-fixtures run-tests join-fixtures]]
#?(:clj [lambdaisland.hiccup :as hiccup]
:cljs [lambdaisland.thicc :as thicc])))
:cljs [lambdaisland.thicc :as thicc]))
#?(:cljs
(:require-macros lambdaisland.ornament-test)))

(defn render [h]
#?(:clj (hiccup/render h {:doctype? false})
Expand Down Expand Up @@ -58,9 +60,8 @@
(def my-tokens {:main-color "green"})

;; Referencing non-defstyled variables in rules is only possible in Clojure
#?(:clj
(o/defstyled with-code :div
{:background-color (-> my-tokens :main-color)}))
(o/defstyled with-code :div
{:background-color (-> my-tokens :main-color)})

;; TODO add assertions for these
(o/defstyled with-media :div
Expand Down Expand Up @@ -131,11 +132,19 @@
:color "#cff9cf"
:text-decoration "underline"})

;; For use in reagent, `::o/attrs` are still propagated to the element
(o/defstyled form-2 :div
([a]
(fn [b]
[:<> "hello"])))

;; Will fail to compile on cljs if the :require-macros line is missing
(o/defstyled with-str :div
{:border (str "1px solid red")}
([props]
[:<> "foo"]))


#?(:clj
(deftest css-test
(is (= ".ot__simple{color:#fff}"
Expand Down

0 comments on commit b1e21c1

Please sign in to comment.