The Holy Java

Building the right thing, building it right, fast

Kioo: How to Troubleshoot Template Processing

Posted by Jakub Holý on April 8, 2014

So you have created an Om/Reagent/React component using a Kioo template and the result is not as you expected, perhaps just an empty element? Let me share what I have discovered about troubleshooting this (though I am no expert). You will se how to invoke the underlying Clojure function component* manually, how to expand the deftemplate macro, how to call the resulting JavaScript function, and what the intermediate forms look like.

Give this HTML template:

<!-- kiootest.html -->
<html>
  <body>
    <div id="content">place holder</div>
  </body>
</html>

and ClojureScript code with Om and Kioo:

(deftemplate kiootest "kiootest.html" [_]
  {[:#content] (kioo/content "#Inserted by Kioo#")})

(om/root #(om/component (kiootest %)) (atom {})
         {:target (. js/document (getElementById "a-container-div"))})

how do we troubleshoot the processing and rendering of the template?

1. Macroexpand with tracing

The first step is to expand the deftemplate macro, which also matches the selectors against the HTML. We will use the wonderful clojure.tools.trace library and its trace-ns to automatically log and calls and return values during the processing to understand what is going on.

In Clojure (not ClojureScript!) REPL that has the template on its classpath:

;; Clojure REPL
(>trace-ns 'kioo.core) ;; this is clojure.tools.trace/trace-ns
;; => nil
(require '[kioo.core :as kh]) ; => nil
(require '[kioo.om :as ko])   ; => nil
(macroexpand-1 '(deftemplate kiootest "kiootest.html" [_]
  {[:content] (kioo/content "#Inserted by Kioo#")}))
;; =>
;;(def kiootest (clojure.core/fn [_]
;;   (clojure.core/let
;;    [ch37298 (kioo.core/flatten-nodes
;;      ["\n    "
;;       (clojure.core/apply om.dom/div
;;        (cljs.core/clj->js {:className nil, :style nil, :id "content"})
;;        (kioo.core/flatten-nodes ["place holder"]))
;;       "\n  "])]
;;    (if (clojure.core/= 1 (clojure.core/count ch37298))
;;     (clojure.core/first ch37298)
;;     (clojure.core/apply om.dom/span nil ch37298)))))

(Side note: this is Clojure’s macroexpand-1. ClojureScript has also one, in cljs.analyze, but it failed for me due to unresolved dependencies.)

We can see that Kioo copies the div#content. However I made a mistake in the selector (:content instead of :#content) so it does not attach the transformation and just preserves the original content, "place holder". Let me fix it:

(macroexpand-1 '(ko/deftemplate kiootest "kiootest.html" [_]
    {[:#content] (kh/content "#Inserted by Kioo#")}))
;; =>
;; (def kiootest (clojure.core/fn [_]
;; (clojure.core/let [ch22901
;;   (kioo.core/flatten-nodes
;;    ["\n    "
;;     ((kioo.core/handle-wrapper kioo.om/make-dom)
;;      ((kh/content "#Inserted by Kioo#") ;; <- attach the content transformation to the div#content
;;       {:tag :div,
;;        :attrs {:className nil, :style nil, :id "content"},
;;        :content (kioo.core/flatten-nodes ["place holder"]),
;;        :sym om.dom/div}))
;;     "\n  "])]
;;  (if (clojure.core/= 1 (clojure.core/count ch22901))
;;   (clojure.core/first ch22901)
;;   (clojure.core/apply om.dom/span nil ch22901)))))

This time I got it right and can see the kh/content transformation applied to the div node (which also has its original :content).

Let’s see what is in the trace log (selected parts; refer to kioo.core to see the important macros and methods – deftemplate, snippet*, component*, map-trans (map, i.e. apply, transformations)):

TRACE t22897: (kioo.core/snippet* "kiootest.html" ({[:#content] (kh/content "#Inserted by Kioo#")}) [_] {:emit-trans ...})
...
TRACE t22899: | | (kioo.core/component* "kiootest.html" ({[:#content] (kh/content "#Inserted by Kioo#")}) {:emit-trans ...})
TRACE t22900: | | | (kioo.core/eval-selector [:body :> #<core$constantly$fn__4051 clojure.core$constantly$fn__4051@5829013f>]) ;; the default root selector [:body :> any-node]
...
TRACE t22902: | | | (kioo.core/map-trans ("\n    " {:tag :div, :attrs {:id "content"}, :content ("place holder")} "\n  ") {[:#content] (kh/content "#Inserted by Kioo#")})
TRACE t22903: | | | | (kioo.core/eval-selector [:#content])
TRACE t22903: | | | | => [:#content]
TRACE t22904: | | | | (kioo.core/attach-transform (kh/content "#Inserted by Kioo#"))
...
TRACE t22902: | | | => ["\n    " {:tag :div, :attrs {:id "content"}, :content ["place holder"],
                        :trans (kh/content "#Inserted by Kioo#")} "\n  "]
... ;; calls to compile, compile-node, producing flatten-nodes etc.

Line #3 will be useful later, line #12 is important because it shows that the transformation has been attached to the expected node. If the selector did not match it (as in the :content case), the result would be different (no :trans):

;; Wrong selector :content, did not match the div => no :trans attached:
TRACE t22887: | | | => ["\n    "
                        {:tag :div,
                         :attrs {:id "content"},
                         :content ["place holder"]}
                        "\n  "]

2. Invoking component* manually

Once we know how is component* invoked , we can call it directly to speed up our experimenting:

;; Based on: TRACE t22899: | | (kioo.core/component* "kiootest.html" ({[:#content] (kh/content "#Inserted by Kioo#")}) {:emit-trans ...})
;; =>
(println (kh/component* "kiootest.html"
               [{[:#content] "DUMMY-KIOO-TRANSF"}] ;; CompilerException No such var: kh/content if using (kh/content "#Inserted by Kioo#") =>
               kh/react-emit-opts))
;; => nil
;; (clojure.core/let
;;  [ch22943
;;   (kioo.core/flatten-nodes
;;    [((kioo.core/handle-wrapper kioo.core/make-dom)
;;      (DUMMY-KIOO-TRANSF
;;       {:tag :div,
;;        :attrs {:className nil, :style nil, :id content},
;;        :content (kioo.core/flatten-nodes [place holder]),
;;        :sym js/React.DOM.div}))])]
;;  (if (clojure.core/= 1 (clojure.core/count ch22943))
;;   (clojure.core/first ch22943)
;;   (clojure.core/apply js/React.DOM.span nil ch22943)))

Notice that I need to wrap the sel+transf map in a vector (or ‘(..))
and that I had to remove (kh/content ...) since it lead to a CompilerException (I guess there is a way to solve that.)

Calling component* directly like this, even with the limitations, makes it possible for me to find out more easily where my transformation ends up.

3. The resulting JavaScript

I can find the following, little daunting but actually not so complicated output in the compiled app.js file, which is direct cljs->js translation of the macro expansion above:

experiment.core.kiootest = function kiootest(_) {
  var ch56433 = kioo.core.flatten_nodes.call(null, new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE,
["\n    ",
kioo.core.handle_wrapper.call(null, kioo.om.make_dom)
  .call(null, kioo.om.content.call(null, "#Inserted by Kioo#")
    .call(null, new cljs.core.PersistentArrayMap(null, 4, [
      // Translation of ':tag :div':
      new cljs.core.Keyword(null, "tag", "tag", 1014018828), new cljs.core.Keyword(null, "div", "div", 1014003715),
      new cljs.core.Keyword(null, "attrs", "attrs", 1107056660),
      new cljs.core.PersistentArrayMap(null,
  3, [new cljs.core.Keyword(null, "className", "className", 1004015509), null, new cljs.core.Keyword(null, "style", "style", 1123684643), null,
        new cljs.core.Keyword(null, "id", "id", 1013907597), "content"], null), // == :id "content"
      new cljs.core.Keyword(null, "content", "content", 1965434859), kioo.core.flatten_nodes.call(null, new cljs.core.PersistentVector(null, 1, 5,
cljs.core.PersistentVector.EMPTY_NODE, ["place holder"], null)),
new cljs.core.Keyword(null, "sym", "sym", 1014018617), om.dom.div], null))),
"\n  "],
  null));
  if (cljs.core._EQ_.call(null, 1, cljs.core.count.call(null, ch56433))) {
    return cljs.core.first.call(null, ch56433);
  } else {
    return cljs.core.apply.call(null, om.dom.span, null, ch56433);
  }
};

Little hard to read but still a direct translation of the cljs code.

We can also call the function in JS console, which returns a JS object representing a React.js component:

> experiment.core.kiootest(null)
Constructor {props: Object, _owner: null, _lifeCycleState: "UNMOUNTED", _pendingProps: null, _pendingCallbacks: null…}
// Expanded and filtered:
Constructor {props: ...
  props: Object
     children: Array[3]
       0: "↵    " // the "\n  " we have seen previously
       1: Constructor
            props: Object
              children: "#Inserted by Kioo#"
              id: "content"
  __proto__: ReactDOMComponent
       2: "↵    "

4. Testing selectors

Selectors are run at compile time and are actually processed by Enlive and can be thus tested with Enlive.

Examples:

(require '[net.cgrand.enlive-html :as e])
(def html "
  <section>
    <form class='classless'></form>
    <div class='animal'>Giraffe</div>
    <div class='animal'>Tapir</div>
  </section>
")
(e/select (e/html-snippet html) [[:.animal (e/nth-of-type 1)]])
=> ({:tag :div, :attrs {:class "animal"}, :content ("Giraffe")})
;; Or, the same thing but reading from a file / classpath:
;; (e/select (e/html-resource (clojure.java.io/file "/tmp/animals.html")) [[:.animal (e/nth-of-type 1)]])
;; (e/select (e/html-resource "/classpath/to/animals.html") [[:.animal (e/nth-of-type 1)]])

Summary

Clojure’s macroexpand-1 with tools.trace are the best tools for troubleshooting Kioo templates. However we can go all the way to the generated JavaScript and resulting React.js object.

Improvements suggestions are welcomed :-).

About these ads

3 Responses to “Kioo: How to Troubleshoot Template Processing”

  1. I love Enlive and Kioo certainly looks like a technologically interesting and valuable project… but isn’t the whole React philosophy about *not* separating the view logic and UI definition using different technologies? Kioo seems to go against what Pete Hunt talks about in http://2013.jsconf.eu/speakers/pete-hunt-react-rethinking-best-practices.html
    Am I missing something?

    • I liked his point about templating being crippled. however we already use Enlive so reusing st. similar for the few dynamic parts makes sense.

    • Kevin Webster said

      I think what Pete was saying that he disliked templating frameworks. Enlive (Kioo) is not a templating framework, its a html transformation library. It is not trying to inject new functionality into HTML. I think Enlive fits well to the react philosophy since the logic should still only be contained within the components, yet the structure is within the HTML.

Sorry, the comment form is closed at this time.

 
%d bloggers like this: