Kioo: How to Troubleshoot Template Processing
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:
and ClojureScript code with Om and Kioo:
how do we troubleshoot the processing and rendering of the template?
In Clojure (not ClojureScript!) REPL that has the template on its classpath:
(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
This time I got it right and can see the
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)):
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
Notice that I need to wrap the sel+transf map in a vector (or '(..)) and that I had to remove
Calling component* directly like this, even with the limitations, makes it possible for me to find out more easily where my transformation ends up.
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:
Examples:
Improvements suggestions are welcomed :-).
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 iscomponent*
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 :-).