Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

first draft of compile template macros

  • Loading branch information...
commit 34a236a31b7495b3927d5ad5516e0164c7e7c956 1 parent fbba2d6
@aria42 aria42 authored
View
2  project.clj
@@ -10,7 +10,7 @@
[:email "admin+oss@getprismatic.com"]
[:timezone "-8"]]]
:plugins [[lein-cljsbuild "0.2.10"]]
- :dependencies [[crate "0.2.3" :scope "dev"]] ;; for perf test
+ :dependencies [[org.clojure/clojure "1.4.0"][crate "0.2.3" :scope "dev"]] ;; for perf test
:hooks [leiningen.cljsbuild]
:cljsbuild
{:builds
View
69 src/dommy/template_compile.clj
@@ -0,0 +1,69 @@
+(ns dommy.template-compile
+ "Clojure macros to generate procedural DOM building when
+ passed nested structured vectors at compile time. Much faster than runtime templating"
+ (:require [clojure.string :as str]))
+
+(defmacro compile-add-attr!
+ "compile-time add attribute"
+ [d k v]
+ (assert (keyword? k))
+ (cond
+ (identical? k :class) `(set! (.-className ~d) (.trim (str (.-className ~d) " " ~v)))
+ (identical? k :style) `(.setAttribute ~d ~(name k) (dommy.template/style-str ~v))
+ (identical? k :classes) `(compile-add-attr! ~d :class ~(str/join " " (map name v)))
+ :else `(.setAttribute ~d ~(name k) ~v)))
+
+(defn parse-keyword
+ "return pair [tag class-str id] where tag is dom tag and attrs
+ are key-value attribute pairs from css-style dom selector"
+ [node-key]
+ (let [node-str (name node-key)
+ node-tag (second (re-find #"^([^.\#]+)[.\#]?" node-str))
+ classes (map #(.substring ^String % 1) (re-seq #"\.[^.*]*" node-str))
+ id (first (map #(.substring ^String % 1) (re-seq #"#[^.*]*" node-str)))]
+ [(if (empty? node-tag) "div" node-tag)
+ (str/join " " classes)
+ id]))
+
+(declare node)
+
+(defmacro compile-compound [[node-key & rest]]
+ (let [literal-attrs (when (map? (first rest)) (first rest))
+ var-attrs (when (and (not literal-attrs) (-> rest first meta :attrs))
+ (first rest))
+ children (drop (if (or literal-attrs var-attrs) 1 0) rest)
+ [tag class-str id] (parse-keyword node-key)
+ dom-sym (gensym "dom")]
+ `(let [~dom-sym (.createElement js/document ~(name tag))]
+ ~@(when-not (empty? class-str)
+ [`(set! (.-className ~dom-sym) ~class-str)])
+ ~@(when id
+ [`(.setAttribute ~dom-sym "id" ~id)])
+ ~@(for [[k v] literal-attrs]
+ (if (keyword? k)
+ `(compile-add-attr! ~dom-sym ~k ~v)
+ `(dommy.template/add-attr! ~dom-sym ~k ~v)))
+ ~@(when var-attrs
+ [`(doseq [[k# v#] ~var-attrs]
+ (dommy.template/add-attr! ~dom-sym k# v#))])
+ ~@(for [c children]
+ `(.appendChild ~dom-sym (node ~c)))
+ ~dom-sym)))
+
+(defmacro node [data]
+ (cond
+ (vector? data) `(compile-compound ~data)
+ (keyword? data) `(compile-compound [~data])
+ (string? data) `(.createTextNode js/document ~data)
+ :else `(dommy.template/-elem ~data)))
+
+(comment
+
+ (compile-compound
+ [:a {:classes ["class1" "class2"] :href "http://somelink"} "anchor"])
+
+ (compile-compound [:a ^:attrs (merge {:class "v"})])
+
+ (parse-keyword :div.class1.class2)
+
+)
View
13 test/dommy/template_perf_test.cljs
@@ -1,6 +1,7 @@
(ns dommy.template-perf-test
(:require [dommy.template :as template]
- [crate.core :as crate]))
+ [crate.core :as crate])
+ (:require-macros [dommy.template-compile :as template-compile]))
;; Perf Test: dommy vs. crate. vs. jQuery
@@ -10,6 +11,13 @@
[:div.class1.class2 {:id (str "item" (:key datum))}
[:span.anchor (:name datum)]]]]))
+
+(defn dommy-compiled [datum]
+ (template-compile/node
+ [:li [:a {:href (str "#show/" (:key datum))}
+ [:div.class1.class2 {:id (str "item" (:key datum))}
+ [:span.anchor (:name datum)]]]]))
+
(defn crate-template [datum]
(crate/html
[:li [:a {:href (str "#show/" (:key datum))}]
@@ -45,7 +53,8 @@
(shuffle
[[:jquery jquery-template]
[:crate crate-template]
- [:dommy dommy-template]])]
+ [:dommy dommy-template]
+ [:dommy-compiled dommy-compiled]])]
(let [ul (-> "<ul>" js/jQuery (.addClass "products"))
secs (run-test ul data li-fn)]
[key secs])))
View
72 test/dommy/template_test.cljs
@@ -1,33 +1,47 @@
(ns dommy.template-test
- (:require [dommy.template :as template]))
+ (:require [dommy.template :as template])
+ (:require-macros [dommy.template-compile :as template-compile]))
+
(defn ^:export simple-test []
(assert (-> :b template/node .-tagName (= "B")))
(assert (-> "some text" template/node .-textContent (= "some text")))
- (let [e (template/node [:span "some text"])]
- (assert (-> e .-tagName (= "SPAN")))
- (assert (-> e .-textContent (= "some text")))
- (assert (-> e .-childNodes (aget 0) .-nodeType (= js/document.TEXT_NODE)))
- (assert (-> e .-children .-length zero?)))
- (let [e (template/node [:a {:classes ["class1" "class2"] :href "http://somelink"} "anchor"])]
- (assert (-> e .-tagName (= "A")))
- (assert (-> e .-textContent (= "anchor")))
- (assert (-> e (.getAttribute "href") (= "http://somelink")))
- (assert (-> e .-className (= "class1 class2"))))
- (let [e (template/base-element :div#id.class1.class2)]
- (assert (-> e .-tagName (= "DIV")))
- (assert (-> e (.getAttribute "id") (= "id")))
- (assert (-> e .-className (= "class1 class2"))))
- (let [e (template/compound-element [:div {:style {:margin-left "15px"}}])]
- (assert (-> e .-tagName (= "DIV")))
- (assert (-> e (.getAttribute "style") (= "margin-left:15px;"))))
- (let [e (template/compound-element [:div.class1 [:span#id1 "span1"] [:span#id2 "span2"]])]
- (assert (-> e .-textContent (= "span1span2")))
- (assert (-> e .-className (= "class1")))
- (assert (-> e .-childNodes .-length (= 2)))
- (assert (-> e .-innerHTML (= "<span id=\"id1\">span1</span><span id=\"id2\">span2</span>")))
- (assert (-> e .-childNodes (aget 0) .-innerHTML (= "span1")))
- (assert (-> e .-childNodes (aget 1) .-innerHTML (= "span2"))))
+ ;; unfortunately to satisfy the macro gods, you need to
+ ;; duplicate the vector literal to test compiled and runtime template
+ (let [e1 (template/node [:span "some text"])
+ e2 (template-compile/node [:span "some text"])]
+ (doseq [e [e1 e2]]
+ (assert (-> e .-tagName (= "SPAN")))
+ (assert (-> e .-textContent (= "some text")))
+ (assert (-> e .-childNodes (aget 0) .-nodeType (= js/document.TEXT_NODE)))
+ (assert (-> e .-children .-length zero?))))
+ (let [e1 (template/node [:a {:classes ["class1" "class2"] :href "http://somelink"} "anchor"])
+ e2 (template-compile/node
+ [:a {:classes ["class1" "class2"] :href "http://somelink"} "anchor"])]
+ (doseq [e [e1 e2]] (assert (-> e .-tagName (= "A")))
+ (assert (-> e .-textContent (= "anchor")))
+ (assert (-> e (.getAttribute "href") (= "http://somelink")))
+ (assert (-> e .-className (= "class1 class2")))))
+ (let [e1 (template/base-element :div#id.class1.class2)
+ e2 (template-compile/node :div#id.class1.class2)]
+ (doseq [e [e1 e2]]
+ (assert (-> e .-tagName (= "DIV")))
+ (assert (-> e (.getAttribute "id") (= "id")))
+ (assert (-> e .-className (= "class1 class2")))))
+ (let [e1 (template/compound-element [:div {:style {:margin-left "15px"}}])
+ e2 (template-compile/node [:div {:style {:margin-left "15px"}}])]
+ (doseq [e [e1 e2]]
+ (assert (-> e .-tagName (= "DIV")))
+ (assert (-> e (.getAttribute "style") (= "margin-left:15px;")))))
+ (let [e1 (template/compound-element [:div.class1 [:span#id1 "span1"] [:span#id2 "span2"]])
+ e2 (template-compile/node [:div.class1 [:span#id1 "span1"] [:span#id2 "span2"]])]
+ (doseq [e [e1 e2]]
+ (assert (-> e .-textContent (= "span1span2")))
+ (assert (-> e .-className (= "class1")))
+ (assert (-> e .-childNodes .-length (= 2)))
+ (assert (-> e .-innerHTML (= "<span id=\"id1\">span1</span><span id=\"id2\">span2</span>")))
+ (assert (-> e .-childNodes (aget 0) .-innerHTML (= "span1")))
+ (assert (-> e .-childNodes (aget 1) .-innerHTML (= "span2")))))
(assert (= "<span id=\"id1\">span1</span><span id=\"id2\">span2</span>"
(-> [:div (for [x [1 2]] [:span {:id (str "id" x)} (str "span" x)])]
template/node
@@ -36,9 +50,11 @@
(assert (-> e .-tagName (= "DIV")))
(assert (-> e .-innerHTML (= "<p>some-text</p>")))
(assert (= e (template/node e))))
- (let [e (template/base-element :#id1.class1)]
- (assert (= (.-outerHTML (template/base-element :div#id1.class1))
- (.-outerHTML (template/base-element :#id1.class1)))))
+ (let [e1 (template/base-element :#id1.class1)
+ e2 (template-compile/node :#id1.class1)]
+ (doseq [e [e1 e2]]
+ (assert (= (.-outerHTML (template/base-element :div#id1.class1))
+ (.-outerHTML (template/base-element :#id1.class1))))))
(.log js/console "PASS simple-test"))
(simple-test)
Please sign in to comment.
Something went wrong with that request. Please try again.