Created
January 9, 2009 15:29
-
-
Save mmcgrana/45136 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
clj-html.core & compojure.html comparison conclusions | |
be sure to see syntax.clj and performance.clj first | |
* Interoperability between clj-html.core and compojure.html might be feasble, as | |
their syntax are similar. | |
* Helper functions must be written to return html Strings, not vector syntax, if | |
they are to be used with both libraries. | |
* clj-html.core is between 1 and 2 orders of magnitude faster than compjure.html. | |
* Users need to decide how they want to balance the speed of clj-html.core with | |
the syntatic flexibility of compojure.html | |
* At least one knows that if an app starts with compojure.html, one could later | |
easily switch to clj-html for performace critial templates. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;; clj-html.core & compojure.html performance | |
(use '[clj-html.core :rename {html htmlc htmli htmli2}] | |
'clj-html.utils | |
'[compojure.html :rename {html htmli}]) | |
(defn- htmli-item-partial | |
[item] | |
[:div.item | |
[:p.special item]]) | |
(defn- htmlc-item-partial | |
[item] | |
(htmlc | |
[:div.item | |
[:p.special item]])) | |
(defn- string-builder-partial | |
[item] | |
(let [builder (StringBuilder.)] | |
(.append builder "<div id=\"item\"><p class=\"special\"") | |
(.append builder item) | |
(.append builder "</div>") | |
(.toString builder))) | |
(defn- htmli-template | |
[html-f items title header] | |
(html-f | |
[:html | |
[:head [:title title]] | |
[:body | |
[:div#container | |
[:div#content {:id (str "foo" "bar") :class (str "biz" "bat")} | |
(if true | |
[:h1#custom-header (str "Custom " header)] | |
[:h1#header header]) | |
[:div#items | |
(map htmli-item-partial items)]]]]])) | |
(defn- htmlc-template | |
[items title header] | |
(htmlc | |
[:html | |
[:head [:title title]] | |
[:body | |
[:div#container | |
[:div#content {:id (str "foo" "bar") :class (str "biz" "bat")} | |
(if true | |
(htmlc [:h1#custom-header (str "Custom " header)]) | |
(htmlc [:h1#header header])) | |
[:div#items | |
(map-str htmlc-item-partial items)]]]]])) | |
(defn- string-builder-template | |
[items title header] | |
(let [builder (StringBuilder.)] | |
(.append builder "<html><head><title>") | |
(.append builder (str title)) | |
(.append builder "</title></head><body><div id=\"container\"><div id=\"content\" id=\"") | |
(.append builder (str "foo" "bar")) | |
(.append builder "\" class=\"") | |
(.append builder (str "biz" "bat")) | |
(.append builder "\">") | |
(if true | |
(do | |
(.append builder "<h1 id=\"custom-header\">") | |
(.append builder (str "Custom" header)) | |
(.append builder "</h1>")) | |
(do | |
(.append builder "<h1 id=\"header\">") | |
(.append builder (str header)) | |
(.append builder "</h1>"))) | |
(doseq [item items] | |
(.append builder (string-builder-partial item))) | |
(.append builder "</div></div></body></html>") | |
(.toString builder))) | |
(def items ["foo" "bar" "bat"]) | |
(def title "title") | |
(def header "header") | |
(defn bench | |
[name f] | |
(print (str name ": ")) | |
(time (dotimes [_ 10000] (f items title header)))) | |
(assert (= (htmlc-template items title header) | |
((partial htmli-template htmli2) items title header))) | |
(dotimes [_ 2] | |
(bench "compojure.html " (partial htmli-template htmli)) | |
(bench "clj-html.core(i) " (partial htmli-template htmli2)) | |
(bench "clj-html.core(c) " htmlc-template) | |
(bench "StringBuilder " string-builder-template) | |
(println)) | |
; $ clj performace.clj | |
; compojure.html : "Elapsed time: 11111.611 msecs" | |
; clj-html.core(i) : "Elapsed time: 1892.746 msecs" | |
; clj-html.core(c) : "Elapsed time: 136.493 msecs" | |
; StringBuilder : "Elapsed time: 66.032 msecs" | |
; | |
; compojure.html : "Elapsed time: 11526.314 msecs" | |
; clj-html.core(i) : "Elapsed time: 1849.2 msecs" | |
; clj-html.core(c) : "Elapsed time: 126.347 msecs" | |
; StringBuilder : "Elapsed time: 62.133 msecs" | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;; clj-html.core & compojure.html syntax | |
; In the examples that follow I will use htmli (denoting 'interpreted') for | |
; compojure.html/html and htmlc ('compiled') for clj-html.core/html: | |
(use '[clj-html.core :rename {html htmlc}] | |
'[compojure.html :rename {html htmli}]) | |
; Generally the two are similar: | |
(htmli [:body [:div#foo "content"]]) | |
"<body>\n <div id=\"foo\">\n content\n </div>\n</body>\n" | |
(htmlc [:body [:div#foo "content"]]) | |
"<body><div id=\"foo\">content</div></body>" | |
; One minor difference: clj-html/html can have multiple literal class names: | |
(htmli [:p.foo.bar "content"]) | |
java.lang.NullPointerException | |
(htmlc [:p.foo.bar "content"]) | |
"<p class=\"foo bar\">content</p>" | |
; For clj-html, all tag keywords must be literal, i.e. locals or vars can not | |
; be used for dynamic tag names: | |
(let [t :p] (htmli [t "content"])) | |
"<p>\n content\n</p>\n" | |
(let [t :p] (htmlc [t "content"])) | |
java.lang.Exception: Unrecognized form [t "content"] | |
; Similarly, for clj-html the attribute map must be a literal map. Keys and | |
; values can be dynamic, but the outermost form must be a literal {}: | |
(let [k :id v "an_id"] (htmli [:p {k v} "content"])) | |
"<p id=\"an_id\">\n content\n</p>\n" | |
(htmli [:p (merge {:id "an_id"} {:class "a_class"}) "content"]) | |
"<p class=\"a_class\" id=\"an_id\">\n content\n</p>\n" | |
(let [k :id v "an_id"] (htmlc [:p {k v} "content"])) | |
"<p id=\"an_id\">content</p>" | |
(htmlc [:p (merge {:id "an_id"} {:class "a_class"}) "content"]) | |
"<p>{:class \"a_class\", :id \"an_id\"}content</p>" | |
; One big set of differences is how the two libraries support | |
; mixing of the literal vector markp syntax with calls to Clojure helper | |
; functions; clj-html generally requires more (html ...) calls inside calls to | |
; helper functions. This is the case becuase in compojure calls to helper | |
; functions can return literal markup for compojure but in clj-html must return | |
; html snippets: | |
(defn compojure-helper [x] [:div.styled x]) | |
(htmli [:div.outer (compojure-helper [:p "inner"])]) | |
"<div class=\"outer\">\n <div class=\"styled\">\n <p>\n inner\n </p>\n </div>\n</div>\n" | |
(defn clj-html-helper [x] (htmlc [:div.styled x])) | |
(htmlc [:div.outer (clj-html-helper (htmlc [:p "inner"]))]) | |
"<div class=\"outer\"><div class=\"styled\"><p>inner</p></div></div>" | |
; Another set of differences is how the two handle rendering content for a | |
; collection. | |
; One case to consider is a helper function that needs to be applied to each | |
; element in a collection: | |
(def items '("foo" "bar")) | |
(defn compojure-partial [x] [:div.item [:p x]]) | |
(htmli (map compojure-partial items)) | |
"<div class=\"item\">\n <p>\n foo\n </p>\n</div>\n<div class=\"item\">\n <p>\n bar\n </p>\n</div>\n" | |
(defn clj-html-partial [x] (htmlc [:div.item [:p x]])) | |
(htmlc (map-str clj-html-partial items)) | |
"<div class=\"item\"><p>foo</p></div><div class=\"item\"><p>bar</p></div>" | |
; Another collection-related case is when we want to write inline the markup | |
; to use for each item: | |
(htmli (for [item items] [:div.item [:p item]])) | |
"<div class=\"item\">\n <p>\n foo\n </p>\n</div>\n<div class=\"item\">\n <p>\n bar\n </p>\n</div>\n" | |
(htmlc (domap-str [item items] (htmlc [:div.item [:p item]]))) | |
"<div class=\"item\"><p>foo</p></div><div class=\"item\"><p>bar</p></div>" | |
; Though with a macro the case for clj-html can be improved: | |
(defmacro htmlfor | |
[[binding-sym coll-form] & body] | |
`(domap-str [~binding-sym ~coll-form] (htmlc ~@body))) | |
(htmlc (htmlfor [item items] [:div.item [:p item]])) | |
; Note that the two libraries currently use slightly differnt utility functions | |
; for helping with iteration: | |
* clj-html.core/map-str and compojure.html/str-map are identical except for name | |
* clj-html.core/domap-str serves a similar goal in clj-html templates as | |
compojure.html/domap serves in compojure templates | |
; Finally, for templates with conditionals we need more (html calls when using | |
; clj-html: | |
(htmli | |
[:div | |
(if-let [maybe-content "content"] | |
[:p maybe-content])]) | |
"<div>\n <p>\n content\n </p>\n</div>\n" | |
(htmlc | |
[:div | |
(if-let [maybe-content "content"] | |
(htmlc [:p maybe-content]))]) | |
"<div><p>content</p></div>" | |
; Though again we could invest in similar macros like htmlif and htmlif-let | |
; for any common cases. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment