Skip to content

Commit

Permalink
Various small speed improvements.
Browse files Browse the repository at this point in the history
Most notably:

- Replace most usages of nested map/filter/zip-map with transducers
- Get rid of `clj->js` in `sablono.interpreter/attributes`
  • Loading branch information
the-kenny authored and r0man committed Jan 8, 2017
1 parent c3eb126 commit 17ec99d
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 72 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.org
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

** Unreleased

- [[https://github.com/r0man/sablono/pull/133][#133]] Improve performance by using transducers.
- [[https://github.com/r0man/sablono/issues/141][#141]] Fix =^:inline= hint for elements without attributes.

** 0.7.6
Expand Down
56 changes: 27 additions & 29 deletions src/sablono/compiler.clj
Original file line number Diff line number Diff line change
Expand Up @@ -14,39 +14,36 @@
(defprotocol IJSValue
(to-js [x]))

(defn compile-map-attr [name value]
{name
(if (map? value)
(to-js value)
`(~'clj->js ~value))})

(defmulti compile-attr (fn [name value] name))

(defmethod compile-attr :class [name value]
{:class
(cond
(or (nil? value)
(keyword? value)
(string? value))
value
(and (or (sequential? value)
(set? value))
(every? string? value))
(join-classes value)
:else `(sablono.util/join-classes ~value))})

(defmethod compile-attr :style [name value]
(compile-map-attr name (camel-case-keys value)))

(defmethod compile-attr :default [name value]
{name (to-js value)})
(defmethod compile-attr :class [_ value]
(cond
(or (nil? value)
(keyword? value)
(string? value))
value
(and (or (sequential? value)
(set? value))
(every? string? value))
(join-classes value)
:else `(sablono.util/join-classes ~value)))

(defmethod compile-attr :style [_ value]
(let [value (camel-case-keys value)]
(if (map? value)
(to-js value)
`(~'clj->js ~value))))

(defmethod compile-attr :default [_ value]
(to-js value))

(defn compile-attrs
"Compile a HTML attribute map."
[attrs]
(->> (seq attrs)
(map #(apply compile-attr %1))
(apply merge)
(reduce (fn [attrs [name value]]some
(assoc attrs name (compile-attr name value) ))
nil)
(html-to-dom-attrs)
(to-js)))

Expand Down Expand Up @@ -292,8 +289,9 @@
"Convert a map into a JavaScript object."
[m]
(JSValue.
(zipmap (keys m)
(map to-js (vals m)))))
(into {}
(map (fn [[k v]] [k (to-js v)]))
m)))

(extend-protocol IJSValue
clojure.lang.Keyword
Expand All @@ -307,7 +305,7 @@
(to-js-map x))
clojure.lang.PersistentVector
(to-js [x]
(JSValue. (vec (map to-js x))))
(JSValue. (mapv to-js x)))
Object
(to-js [x]
x)
Expand Down
6 changes: 4 additions & 2 deletions src/sablono/core.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
(if (map? (first args))
(let [[tag & body] (apply func (rest args))]
(if (map? (first body))
(apply vector tag (merge (first body) (first args)) (rest body))
(apply vector tag (first args) body)))
(into [tag (merge (first body) (first args))]
(rest body))
(into [tag (first args)]
body)))
(apply func args))))

(defn- update-arglists [arglists]
Expand Down
2 changes: 1 addition & 1 deletion src/sablono/interpreter.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
(defn- interpret-seq
"Interpret the seq `x` as HTML elements."
[x]
(into [] (map interpret) x))
(map interpret x))

#?(:cljs
(defn element
Expand Down
45 changes: 28 additions & 17 deletions src/sablono/normalize.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@
(defn compact-map
"Removes all map entries where the value of the entry is empty."
[m]
(reduce
(fn [m k]
(let [v (get m k)]
(if (empty? v)
(dissoc m k) m)))
m (keys m)))
(when m
(into {}
(remove (fn [[_ v]] (empty? v)))
m)))

(defn class-name
[x]
Expand Down Expand Up @@ -74,15 +72,17 @@
"Like clojure.core/merge but concatenate :class entries."
[& maps]
(let [maps (map attributes maps)
classes (map :class maps)
classes (vec (apply concat classes))]
(cond-> (apply merge maps)
(not (empty? classes))
(assoc :class classes))))
classes (mapcat :class maps)]
(when (seq maps)
(cond-> (reduce into {} maps)
(not (empty? classes))
(assoc :class (vec classes))))))

(defn strip-css
"Strip the # and . characters from the beginning of `s`."
[s] (if s (str/replace s #"^[.#]" "")))
[s]
(when s
(str/replace s #"^[.#]" "")))

(defn match-tag
"Match `s` as a CSS tag and return a vector of tag name, CSS id and
Expand All @@ -92,13 +92,17 @@
[tag-name names]
(cond (empty? matches)
(throw (ex-info (str "Can't match CSS tag: " s) {:tag s}))

(#{\# \.} (ffirst matches)) ;; shorthand for div
["div" matches]

:default
[(first matches) (rest matches)])]
[tag-name
(first (map strip-css (filter #(= \# (first %1)) names)))
(vec (map strip-css (filter #(= \. (first %1)) names)))]))
(strip-css (some #(when (= \# (first %1)) %1) names))
(into []
(comp (filter #(= \. (first %1))) (map strip-css))
names)]))

(defn children
"Normalize the children of a HTML element."
Expand All @@ -108,27 +112,34 @@
'()
(string? x)
(list x)

(util/element? x)
(list x)
(and (list? x) (symbol? (first x)))
(list x)

(list? x)
x

(and (sequential? x)
(= (count x) 1)
(sequential? (first x))
(not (string? (first x)))
(not (util/element? (first x)))
(= (count x) 1))
(not (util/element? (first x))))
(children (first x))

(sequential? x)
x

:else (list x))
(remove nil?)))

(defn element
"Ensure an element vector is of the form [tag-name attrs content]."
[[tag & content]]
(when (not (or (keyword? tag) (symbol? tag) (string? tag)))
(when-not (or (keyword? tag)
(symbol? tag)
(string? tag))
(throw (ex-info (str tag " is not a valid element name.") {:tag tag :content content})))
(let [[tag id class] (match-tag tag)
tag-attrs (compact-map {:id id :class class})
Expand Down
35 changes: 17 additions & 18 deletions src/sablono/util.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -14,34 +14,35 @@
(defn as-str
"Converts its arguments into a string using to-str."
[& xs]
(apply str (map to-str xs)))
(str/join (map to-str xs)))

(defn camel-case
"Returns camel case version of the key, e.g. :http-equiv becomes :httpEquiv."
[k]
(if (or (keyword? k)
(string? k)
(symbol? k))
(let [[first-word & words] (str/split (name k) #"-")]
(let [[first-word & words] (.split (name k) "-")]
(if (or (empty? words)
(= "aria" first-word)
(= "data" first-word))
k (-> (map str/capitalize words)
(conj first-word)
str/join
keyword)))
k
(-> (map str/capitalize words)
(conj first-word)
str/join
keyword)))
k))

(defn camel-case-keys
"Recursively transforms all map keys into camel case."
[m]
(if (map? m)
(let [ks (keys m)
kmap (zipmap ks (map camel-case ks))]
(-> (rename-keys m kmap)
(cond->
(map? (:style m))
(update-in [:style] camel-case-keys))))
(let [m (into {}
(map (fn [[k v]] [(camel-case k) v]))
m)]
(cond-> m
(map? (:style m))
(update :style camel-case-keys)))
m))

(defn element?
Expand All @@ -61,12 +62,10 @@
(defn join-classes
"Join the `classes` with a whitespace."
[classes]
(->> (map #(cond
(string? %) %
:else (seq %))
classes)
(flatten)
(remove nil?)
(->> classes
(into [] (comp
(mapcat (fn [x] (if (string? x) [x] (seq x))))
(remove nil?)))
(str/join " ")))

(defn react-fn
Expand Down
2 changes: 1 addition & 1 deletion test/sablono/compiler_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@

(deftest test-compile-attr-class
(are [form expected]
(= {:class expected} (compile-attr :class form))
(= expected (compile-attr :class form))
nil nil
"foo" "foo"
'("foo" "bar" ) "foo bar"
Expand Down
8 changes: 4 additions & 4 deletions test/sablono/interpreter_test.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
(= expected (js->clj (i/attributes attrs)))
{} {}
{:className ""} {}
{:className "aa"} {"className" "aa"}
{:className "aa bb"} {"className" "aa bb"}
{:className ["aa bb"]} {"className" "aa bb"}
{:className "aa"} {"className" "aa"}
{:className "aa bb"} {"className" "aa bb"}
{:className ["aa bb"]} {"className" "aa bb"}
{:className '("aa bb")} {"className" "aa bb"}
{:id :XY} {"id" "XY"}))
{:id :XY} {"id" "XY"}))

(deftest test-interpret-shorthand-div-forms
(is (= (interpret [:#test.klass1])
Expand Down

0 comments on commit 17ec99d

Please sign in to comment.