Skip to content
Baptiste Fontaine edited this page Jan 31, 2019 · 12 revisions

Avoiding ambiguity of attributes / children

You can explicitly set the attributes to {} and avoid the ambiguity of children vs attributes that would need to be resolved during runtime. Observe:

(macroexpand-1 '(html [:a (foo)]))
=>
(clojure.core/let
 [attrs99902 (foo)]
 (clojure.core/apply
  js/React.createElement
  "a"
  (if (clojure.core/map? attrs99902) 
        (sablono.interpreter/attributes attrs99902) 
        nil)
  (if (clojure.core/map? attrs99902) 
        nil 
        [(sablono.interpreter/interpret attrs99902)])))


(macroexpand-1 '(html [:a {} (foo)]))
=> (js/React.createElement "a" nil (sablono.interpreter/interpret (foo)))

Avoid for

Not really sablono specific but I think it fits here: for macro generates a ton of code because it is very powerful. Often, though, you don't need the lazyness of for and the flexibility and would be better off with a simple mapv.

(macroexpand-1 '(for [x y]
                  [:span "hi"]))
; => <omitted since it's very very long>

Note also that the body (here: [:span "hi"]) will be emitted twice, which can lead to even larger JS code when your body is big.

There is much less powerful and eager macros here: https://gist.github.com/rauhs/d49d6f8a6f5fbb8230647c5b2ac210b2

Avoid interpretation call

You can further add a type hint to avoid the call to interpret:

(macroexpand-1 '(html [:a {} ^String (foo)]))
=> (js/React.createElement "a" nil (foo))

Obviously, only do this if your (foo) function call returns React elements.

Note that, even though the type hint says ^String, the function (or variable) doesn't have to return a string. This is only used because this type hint is the only one that sablono recognizes and removes the interpretation call. The function can also return a vector or list of (React) elements.

(Hack) Manually minify calls to React.createElement

UPDATE: For a non-compressed production build the difference of the following was around 4kb for me, however, with gzip and brotli the difference was less than 400 bytes. So the following might not be worth it:

Use at your own risk.

Since calls to React.createElement don't get minified by the google closure compiler, you can manually create an alias and rewrite the code generated by sablono: In your clj macros file:

(defmacro html
  "Rewrites (js/React.createElement ...)
   to (js/R ...)
   needs proper externs."
  [body]
  (let [sab (sablono.compiler/compile-html body)]
    (clojure.walk/postwalk-replace {'js/React.createElement 'js/R} sab)))

You should also add an externs file containing:

var R = function(type, props, children) {};

and further create the alias somewhere before you render anything:

(set! js/R js/React.createElement)

and then your code will look like this:

(macroexpand-1 '(html [:a "hi"]))
=> (js/R "a" nil "hi")