Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/onionpancakes/chassis
Fast HTML5 serialization for Clojure
https://github.com/onionpancakes/chassis
clojure hiccup html serialization template
Last synced: about 1 month ago
JSON representation
Fast HTML5 serialization for Clojure
- Host: GitHub
- URL: https://github.com/onionpancakes/chassis
- Owner: onionpancakes
- License: mit
- Created: 2024-01-27T06:42:17.000Z (10 months ago)
- Default Branch: master
- Last Pushed: 2024-04-14T15:22:39.000Z (7 months ago)
- Last Synced: 2024-09-29T17:22:49.961Z (about 2 months ago)
- Topics: clojure, hiccup, html, serialization, template
- Language: Clojure
- Homepage:
- Size: 861 KB
- Stars: 97
- Watchers: 2
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Chassis
Fast HTML5 serialization for Clojure.
Renders [Hiccup](https://github.com/weavejester/hiccup/) style HTML vectors to strings.
Highly optimized runtime serialization without macros. Even faster serialization when combined with compiling macros.
* See [Compiling Elements](#compiling-elements).
* See [Performance](#performance).# Status
[![Run tests](https://github.com/onionpancakes/chassis/actions/workflows/run_tests.yml/badge.svg)](https://github.com/onionpancakes/chassis/actions/workflows/run_tests.yml)
Production released.
# Deps
Add one of these deployments to `deps.edn`.
### GitHub
```clojure
dev.onionpancakes/chassis {:git/url "https://github.com/onionpancakes/chassis"
:git/tag "v1.0.365" :git/sha "3e98fdc"}
```### Clojars
```clojure
dev.onionpancakes/chassis {:mvn/version "1.0.365"}
```# Example
### Runtime HTML Serialization
```clojure
(require '[dev.onionpancakes.chassis.core :as c])(defn my-post
[post]
[:div {:id (:id post)}
[:h2.title (:title post)]
[:p.content (:content post)]])(defn my-blog
[data]
[c/doctype-html5 ; Raw string for
[:html
[:head
[:link {:href "/css/styles.css" :rel "stylesheet"}]
[:title "My Blog"]]
[:body
[:h1 "My Blog"]
(for [p (:posts data)]
(my-post p))]]])(let [data {:posts [{:id "1" :title "foo" :content "bar"}]}]
(c/html (my-blog data)));; "My Blog
My Blog
"foo
bar
```### Compiled HTML Serialization
```clojure
(require '[dev.onionpancakes.chassis.core :as c])
(require '[dev.onionpancakes.chassis.compiler :as cc])(defn my-post-compiled
[post]
(cc/compile
[:div {:id (:id post)}
[:h2.title (:title post)]
[:p.content (:content post)]]))(defn my-blog-compiled
[data]
(cc/compile
[c/doctype-html5 ; Raw string for
[:html
[:head
[:link {:href "/css/styles.css" :rel "stylesheet"}]
[:title "My Blog"]]
[:body
[:h1 "My Blog"]
(for [p (:posts data)]
(my-post-compiled p))]]]))(let [data {:posts [{:id "1" :title "foo" :content "bar"}]}]
(c/html (my-blog-compiled data)));; "My Blog
My Blog
"foo
bar
```# Usage
Require the namespace.
```clojure
(require '[dev.onionpancakes.chassis.core :as c])
```## Elements
Use `c/html` function to generate HTML strings from vectors.
Vectors with **global keywords** in the head position are treated as normal HTML elements. The keyword's name is used as the element's tag name.
```clojure
(c/html [:div "foo"]);; "
foo"
```Maps in the second position are treated as attributes. Use **global keywords** to name attribute keys.
```clojure
(c/html [:div {:id "my-id"} "foo"]);; "
foo"
``````clojure
;; Strings also accepted, but discouraged.
;; Use when keywords cannot encode the desired attribute name.
(c/html [:div {"id" "my-id"} "foo"]);; "
foo"
```The rest of the vector is treated as the element's content. They may be of any type including other elements. Sequences, eductions, and [non-element vectors](#non-element-vectors) are logically flattened with the rest of the content.
```clojure
(c/html [:div {:id "my-id"}
"foo"
(for [i (range 3)] i)
"bar"]);; "
foo012bar"
```## Id and Class Sugar
Like Hiccup, id and class attributes can be specified along with the tag name using css style `#` and `.` syntax.
```clojure
(c/html [:div#my-id.my-class "foo"]);; "
foo"
``````clojure
;; Multiple '.' classes concatenates
(c/html [:div.my-class-1.my-class-2 "foo"]);; "
foo"
``````clojure
;; '.' classes concatenates with :class keyword
(c/html [:div.my-class-1 {:class "my-class-2"} "foo"]);; "
foo"
``````clojure
;; First '#' determines the id.
;; Extra '#' are uninterpreted.
(c/html [:div## "foo"]);; "
foo"(c/html [:div#my-id.my-class-1#not-id "foo"])
;; "
foo"
```However there are differences from Hiccup.
```clojure
;; '#' id takes precedence over :id keyword
(c/html [:div#my-id {:id "not-my-id"} "foo"]);; "
foo"
``````clojure
;; '#' id can be place anywhere
(c/html [:div.my-class-1#my-id "foo"]);; "
foo"
``````clojure
;; '#' id can be place in-between, but don't do this.
;; It will be slightly slower.
(c/html [:div.my-class-1#my-id.my-class-2 "foo"]);; "
foo"
```## Boolean Attributes
Use `true`/`false` to toggle boolean attributes.
```clojure
(c/html [:button {:disabled true} "Submit"]);; "Submit"
(c/html [:button {:disabled false} "Submit"])
;; "Submit"
```## Composite Attribute Values
Collections of attribute values are concatenated as spaced strings.
```clojure
(c/html [:div {:class ["foo" "bar"]}]);; "
"(c/html [:div {:class #{:foo :bar}}])
;; "
"
```Maps of attribute values are concatenated as style strings.
```clojure
(c/html [:div {:style {:color :red
:border "1px solid black"}}]);; "
"
```Attribute collections and maps arbitrarily nest.
```clojure
(c/html [:div {:style {:color :red
:border [:1px :solid :black]}}]);; "
"
```## Write to Appendable
Avoid intermediate allocation by writing directly to [`java.lang.Appendable`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Appendable.html) using the `c/write-html` function.
However, [`java.lang.StringBuilder`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/StringBuilder.html) is highly optimized and it may be faster to write to it (and then write the string out) than to write to the Appendable directly. Performance testing is advised.
```clojure
(let [out (get-appendable-from-somewhere)]
(c/write-html out [:div "foo"]))
```## Escapes
Text and attribute values are escaped by default.
```clojure
(c/html [:div "& < >"]);; "
& < >"(c/html [:div {:foo "& < > \" '"}])
;; "
"
```Escaping can be disabled locally by wrapping string values with `c/raw`.
```clojure
(c/html [:div (c/raw "foo
")]);; "
"foo
```Escaping can be disabled globally by altering vars. Change `c/escape-text-fragment` and `c/escape-attribute-value-fragment` to
`identity` function to allow fragment values to pass through unescaped.Then use `c/escape-text` and `c/escape-attribute-value` to escape locally.
```clojure
(alter-var-root #'c/escape-text-fragment (constantly identity))
(alter-var-root #'c/escape-attribute-value-fragment (constantly identity))(c/html [:div "
foo
"]);; "
"foo
(c/html [:div (c/escape-text "foo & bar")])
;; "
foo & bar"
```### Vetted Unescaped Types
For performance, `java.lang.Number` and `java.util.UUID` are not escaped by default.
### Tags and Attribute Keys Are Not Escaped!
Element tags and attribute keys are not escaped. Be careful when placing dangerous text in these positions.
```clojure
;; uhoh
(c/html [:<> "This is bad!"]);; "<<>>This is bad!<>>"
(c/html [:div {:<> "This is bad!"}])
;; "
=\"This is bad!\">"
```## Non-Element Vectors
Only vectors beginning with keywords are interpreted as elements. Vectors can set their metadata `{::c/content true}` to avoid being interpreted as elements, even if they begin with keywords.
```clojure
;; Not elements
(c/html [0 1 2]) ; => "012"
(c/html ["foo" "bar"]) ; => "foobar"
(c/html ^::c/content [:foo :bar]) ; => "foobar";; Use this to generate fragments of elements
(c/html [[:div "foo"]
[:div "bar"]]) ; "foobar"
```## Non-Attribute Keys
Only **global keywords** and **strings** are interpreted as attribute keys. Everything else is ignored.
```clojure
(c/html [:div {:foo/bar "not here!"}]);; "
"
```## Alias Elements
Alias elements are user defined elements. They resolve to other elements through the `c/resolve-alias` multimethod. They must begin with **namespaced keywords**.
Define alias elements by extending `c/resolve-alias` multimethod on a namespaced keyword. It accepts the following 3 arguments of types:
1. Tag keyword. Used for the dispatch.
2. Attributes map or nil if attrs is absent.
3. Content vector, possibly empty if no content.When implementing aliases, consider the following points:
* Because namespaced keywords are ignored as attributes, they can be used as arguments for alias elements.
* The attributes map will contain `#id` and `.class` merged from the element tag. By placing the alias element's attribute map as the attribute map of a resolved element, the attributes transfers seamlessly between the two.
* The content vector has metadata `{::c/content true}` to avoid being interpreted as an element.```clojure
;; Capitalized name optional, just to make it distinctive.
(defmethod c/resolve-alias ::Layout
[_ {:layout/keys [title] :as attrs} content]
[:div.layout attrs ; Merge attributes
[:h1 title]
[:main content]
[:footer "Some footer message."]])(c/html [::Layout#blog.dark {:layout/title "My title!"}
[:p "My content!"]]);; "
"My title!
My content!
Some footer message.
```## Stateful Values
Instances of `clojure.lang.IDeref` and `clojure.lang.Fn` are automatically dereferenced at serialization. Functions are invoked on their zero argument arity.
Whether or not if this is a good idea is left to the user.
```clojure
(defn current-year []
(.getValue (java.time.Year/now)))(c/html [:footer "My Company Inc " current-year])
;; "My Company Inc 2024"
``````clojure
(def delayed-thing
(delay "delayed"))(c/html [:div {:foo delayed-thing}])
;; "
"
```They can even deference into other elements.
```clojure
(defn get-children []
[:p "Child element"])(c/html [:div.parent get-children])
;; "
"Child element
```## Token and HTML Serializers
Use `c/token-serializer` and `c/html-serializer` to access individual tokens and fragment instances. The underlying type implements `clojure.lang.IReduceInit` and is intended to be used in a reduce.
```clojure
(->> (c/token-serializer [:div "foo"])
(eduction (map type))
(vec));; [dev.onionpancakes.chassis.core.OpeningTag
;; java.lang.String
;; dev.onionpancakes.chassis.core.ClosingTag]
``````clojure
(->> (c/html-serializer [:div "foo"])
(vec));; ["
" "foo" ""]
```## RawString Constants
### DOCTYPE
Use `c/doctype-html5`, a `RawString` wrapping ``. Because it is a `RawString`, it is safe to wrap in a vector to concatenate with the rest of the HTML document.
```clojure
(c/html [c/doctype-html5 [:html "..."]]);; "..."
```###
Use the `c/nbsp` constant.
```clojure
(c/html [:div "foo" c/nbsp "bar"]);; "
foo bar"
```# Compiling Elements
Require the namespace.
```clojure
(require '[dev.onionpancakes.chassis.compiler :as cc])
```## Compile Examples
Slap a `cc/compile` wherever speed is needed! Then call `c/html` like normal to generate HTML.
```clojure
;; In defs
(def global-element
(cc/compile [:div "foo"]));; In defns
(defn fn-element
[arg]
(cc/compile [:div "foo" arg "bar"]));; In aliases
(defmethod c/resolve-alias ::MyElement
[_ attrs content]
(cc/compile
[:div
[:p attrs content]]));; In fn args
(fn-element (cc/compile [:p "some content"]));; Then call c/html like normal to generate HTML.
(c/html (fn-element 123));; "
foo123bar"
```## Compile Usage
Chassis provides compiling macros `cc/compile` and `cc/compile*`. They take **one** argument, the root HTML tree, and they return compiled versions of the HTML tree. Use them to compile elements and pass their results to `c/html`.
```clojure
(defn my-element []
(cc/compile
[:div [:p "foobar"]]))(c/html (my-element))
;; "
"foobar
```Compiling **flattens** and **compacts** the HTML tree, making subsequent calls to `c/html` much faster.
```clojure
(macroexpand-1 '(cc/compile [:div [:p "foobar"]]));; Results in:
#object[dev.onionpancakes.chassis.core.RawString 0x11c2d9a2 ""]foobar
(let [body (identity "some-dynamic-content")]
(pprint
(macroexpand-1
'(cc/compile
[:div.deeply
[:div.nested
[:div.thing
[:p "before" body "after"]]]]))));; Results in:
[#object[dev.onionpancakes.chassis.core.RawString 0x66fd28ce ""]]before"]
body
#object[dev.onionpancakes.chassis.core.RawString 0xe9c5af6 "after
```Use `cc/compile` for most purposes. For performance, the returned value may or may not be a vector. This is so that compiling small fragments of fully compacted HTML (like `
`) is as efficient as possible when iterated over by `c/html`.```clojure
;;
is not wrapped as a 1-sized vector
(cc/compile [:hr]);; #object[dev.onionpancakes.chassis.core.RawString 0x6ba58490 "
"];; The end result is the same either way,
;; but the runtime serialization is faster this way.
(->> (range 10)
(interpose (cc/compile [:hr]))
(c/html));; "0
1
2
3
4
5
6
7
8
9"
```Use `cc/compile*` to ensure the return value is a vector. Otherwise, it is the same as `cc/compile`.
```clojure
;;
is wrapped as a 1-sized vector
(cc/compile* [:hr]);; [#object[dev.onionpancakes.chassis.core.RawString 0x24f1caeb "
"]]
```### Compiled Elements Must Have Literal Tags
A small but subtle difference between `cc/compile` and `c/html` is that `cc/compile` assumes elements are **literal** vectors with **literal** keyword tags. Vectors without literal tags, after [var resolution](#var-resolved-constants), are assumed to be content.
```clojure
;; Basically don't do this.
(let [footag :div]
(c/html (cc/compile [footag "It's foobarred."])));; "divIt's foobarred."
;; Works at runtime.
(let [footag :div]
(c/html [footag "It's foobarred."]));; "
It's foobarred."
```## Ambiguous Attributes Produce Speed Bumps
Ambiguous objects in the second position forces the compiler to emit checks which examine the potential attributes map at runtime.
```clojure
(let [data {:body "foo"}]
(pprint (clojure.walk/macroexpand-all
;; Compiler can't see what (:body data) returns.
'(cc/compile [:div (:body data)]))));; Results in:
[(let*
[attrs13712 (:body data)]
(if ;; Check if 2nd item is attrs map at runtime.
(dev.onionpancakes.chassis.core/attrs? attrs13712)
(dev.onionpancakes.chassis.core/->OpeningTag
nil
:div
nil
nil
attrs13712)
[#object[dev.onionpancakes.chassis.core.RawString 0x1cc8f6fb ""]"]]
attrs13712]))
#object[dev.onionpancakes.chassis.core.RawString 0x6753cbe6 "
```### Resolving Ambiguity - Force Attributes Absence
Use `nil` in second position to force compile the element without attributes.
```clojure
(let [data {:body "foo"}]
(pprint (macroexpand-1
'(cc/compile [:div nil (:body data)]))));; Results in:
[#object[dev.onionpancakes.chassis.core.RawString 0x6e42ae2e ""]"]]
(:body data)
#object[dev.onionpancakes.chassis.core.RawString 0x588c9f7d "
```### Resolving Ambiguity - Force Attributes Presence
Type hint the second position with either `java.util.Map` or `clojure.lang.IPersistentMap` to force compile elements with attributes.
```clojure
(let [data {:attrs {:foo "bar"}
:body "foo"}]
(pprint (macroexpand-1
'(cc/compile [:div ^java.util.Map (:attrs data) (:body data)]))));; Results in:
[(dev.onionpancakes.chassis.core/->OpeningTag
nil
:div
nil
nil
(:attrs data))
(:body data)
#object[dev.onionpancakes.chassis.core.RawString 0x6314faa ""]]
```Type hinting the argument or bindings also works.
* Note: It doesn't show up correctly in a `macroexpand`, but it does works normally. This is because `cc/compile` examines the type hints from macro implied arg `&env`, and `macroexpand` for some reason doesn't capture `&env`.```clojure
;; Should work!
(defmethod c/resolve-alias ::CompileWithAttrs
[_ ^java.util.Map attrs content]
(cc/compile [:div attrs content]))(let [^java.util.Map attrs {:foo "bar"}]
(cc/compile [:div attrs "foobar"]))
```### Vetted Attributes Core Functions
Certain functions in `clojure.core` which returns maps are considered as attributes when called in the second position. Type hinting these invocations is not necessary. They include:
* `array-map`
* `hash-map`
* `sorted-map`
* `sorted-map-by`
* `assoc`
* `assoc-in`
* `merge`
* `select-keys`
* `update-keys`
* `update-vals````clojure
;; Useful in aliases when merging attrs.
(defmethod c/resolve-alias ::AliasWithAttrsMerge
[_ attrs content]
(cc/compile
[:div (merge {:foo "bar"} attrs)
content]))
```### Warn on Ambiguous Attributes
Call `(cc/set-warn-on-ambig-attrs!)` to turn on warnings when compiling elements with ambiguous attributes. It will add a tap which prints out warning messages to `*err*` whenever ambiguous attributes are compiled.
Call `(cc/unset-warn-on-ambig-attrs!)` to disable.
## Compilation Barriers
### Function Calls
Functions calls, and generally any list values, block compilation traversal. Call `cc/compile` again to compile forms within.
```clojure
(defn comp-blocked
[]
[:p "blocked"])(cc/compile [:div "foo" (comp-blocked) "bar"])
;; Results in:
[#object[dev.onionpancakes.chassis.core.RawString 0x67574bda "foo"]"]]
[:p "blocked"]
#object[dev.onionpancakes.chassis.core.RawString 0x565edf06 "bar
```### Alias Elements
Alias elements are implemented as `c/resolve-alias` (via `c/resolve-alias-with-meta`) function calls. As a result, they also block compilation. However, the arguments passed to `c/resolve-alias` will be compiled.
```clojure
(defmethod c/resolve-alias ::CompileMyAlias
[_ attrs content]
[:div attrs content])(pprint
(clojure.walk/macroexpand-all
'(cc/compile
[::CompileMyAlias {:foo "bar"}
[:p "content 1"]
[:p "content 2"]])));; Results in:
(dev.onionpancakes.chassis.core/resolve-alias-with-meta
nil
:user/CompileMyAlias
{:foo "bar"}
[#object[dev.onionpancakes.chassis.core.RawString 0x34e3a7d6 "content 1
content 2
"]])
```### Macro Calls
Macros are expanded during compilation. Like function calls, those which expand into lists block compilation.
```clojure
(pprint
(cc/compile
[:ol
(for [i (range 4)]
[:li i])]));; Results in:
[[#object[dev.onionpancakes.chassis.core.OpeningTag 0x6e462cc4 ""]
"]]
([:li 0] [:li 1] [:li 2] [:li 3])]
#object[dev.onionpancakes.chassis.core.RawString 0x27b55932 ";; Manually call compile in the inner form to reach inside.
(pprint
(cc/compile
[:ol
(for [i (range 4)]
(cc/compile [:li i]))]))
```Macros which expand into non-lists can participate in compilation. Therefore, it is possible to use macros to abstract element components in a compile friendly way.
Whether or not if this is a good idea is left to the user.
```clojure
(defmacro NonBlockingElement
[content]
[:p nil content])(cc/compile [:div (NonBlockingElement "not-blocked")])
;; Results in:
#object[dev.onionpancakes.chassis.core.RawString 0x31b2d0a8 ""]not-blocked
```## Var Resolved Constants
Symbols referring to **vars** containing **constant values** are **resolved** to those values during compilation traversal, thereby allowing those constant values to participate in compilation. Constant types include `String`, `Long`, `IPersistentCollection` of constants, and `RawString` such as `c/doctype-html5` and `c/nbsp`. Use `cc/constant?` to check if values are constants.
```clojure
;; Fully compacted!
;; Even with a symbol splitting content in the middle.
(cc/compile [:div "foo" c/nbsp "bar"]);; Results in:
#object[dev.onionpancakes.chassis.core.RawString 0x7fb21735 "foo bar"]
```## Runtime Compilation
Chassis provides two analogous compile functions, `cc/compile-node` and `cc/compile-node*`, for compiling HTML tree at runtime. They are useful for compiling static HTML pages or components.
Because compiling happens at runtime, lists, function calls, and alias elements are no longer compilation barriers and ambiguous attributes are not possible.
Runtime compilation is similar to generating HTML with `c/html` but with key differences:
* The return values are `c/raw` strings, allowing the result to be embedded in other HTML components without the HTML tags being escaped.
* Stateful values, such as functions and derefs, are not realized.```clojure
(defn current-time []
(java.time.LocalTime/now))(defmethod c/resolve-alias ::CurrentTime
[_ _ _]
[:p "Current time is: " current-time])(def static-page
(cc/compile-node
[::CurrentTime]));; Results in:
[#object[dev.onionpancakes.chassis.core.RawString 0x7a702aaf "Current time is: "]
"]]
;; Notice current-time function is not yet called.
#object[user$current_time 0x584d9dc4 "user$current_time@584d9dc4"]
#object[dev.onionpancakes.chassis.core.RawString 0x1c59c510 ";; Stateful values realized on call to c/html
(c/html static-page);; "
Current time is: 13:48:14.228299269
"
```# Performance
At this time, benchmarks shows Chassis to be 2x faster (and often more!) when compared to other Clojure HTML templating libraries on equivalent benchmark examples.
See bench results in the resource folder.
```clojure
$ clj -M:dev
Clojure 1.11.1;; Chassis
user=> (quick-bench (chassis-page data-mid))
Evaluation count : 2712 in 6 samples of 452 calls.
Execution time mean : 229.730870 µs
Execution time std-deviation : 7.583674 µs
Execution time lower quantile : 221.593639 µs ( 2.5%)
Execution time upper quantile : 237.951723 µs (97.5%)
Overhead used : 8.800684 ns
nil
user=> (quick-bench (chassis-page-compiled data-mid))
Evaluation count : 4722 in 6 samples of 787 calls.
Execution time mean : 131.554387 µs
Execution time std-deviation : 4.400562 µs
Execution time lower quantile : 127.024648 µs ( 2.5%)
Execution time upper quantile : 137.206151 µs (97.5%)
Overhead used : 8.800684 ns
nil
user=> (quick-bench (chassis-page-compiled-unambig data-mid))
Evaluation count : 6186 in 6 samples of 1031 calls.
Execution time mean : 100.309952 µs
Execution time std-deviation : 3.392984 µs
Execution time lower quantile : 98.074419 µs ( 2.5%)
Execution time upper quantile : 105.031335 µs (97.5%)
Overhead used : 8.800684 ns
nil;; Hiccup
user=> (quick-bench (hiccup-page data-mid))
Evaluation count : 990 in 6 samples of 165 calls.
Execution time mean : 615.536499 µs
Execution time std-deviation : 15.886454 µs
Execution time lower quantile : 599.567903 µs ( 2.5%)
Execution time upper quantile : 637.703394 µs (97.5%)
Overhead used : 8.800684 ns
nil
user=> (quick-bench (hiccup-page-compiled data-mid))
Evaluation count : 1044 in 6 samples of 174 calls.
Execution time mean : 594.160734 µs
Execution time std-deviation : 15.249740 µs
Execution time lower quantile : 576.246477 µs ( 2.5%)
Execution time upper quantile : 611.946104 µs (97.5%)
Overhead used : 8.800684 ns
nil
user=> (quick-bench (hiccup-page-compiled-unambig data-mid))
Evaluation count : 2544 in 6 samples of 424 calls.
Execution time mean : 246.390352 µs
Execution time std-deviation : 6.001164 µs
Execution time lower quantile : 240.872342 µs ( 2.5%)
Execution time upper quantile : 255.422063 µs (97.5%)
Overhead used : 8.800684 ns
nil;; Selmer
user=> (quick-bench (selmer-page data-mid))
Evaluation count : 1428 in 6 samples of 238 calls.
Execution time mean : 455.954085 µs
Execution time std-deviation : 14.867158 µs
Execution time lower quantile : 443.374807 µs ( 2.5%)
Execution time upper quantile : 478.302764 µs (97.5%)
Overhead used : 8.800684 ns
nil;; Enlive
user=> (quick-bench (enlive-page-item-html data-mid))
Evaluation count : 282 in 6 samples of 47 calls.
Execution time mean : 2.254892 ms
Execution time std-deviation : 83.779038 µs
Execution time lower quantile : 2.156587 ms ( 2.5%)
Execution time upper quantile : 2.341325 ms (97.5%)
Overhead used : 8.800684 ns
nil
```## Element Vector Allocation is Small
Element vector allocation accounts for a small % of the runtime cost.
```clojure
user=> (quick-bench (page-doall data-mid))
Evaluation count : 34752 in 6 samples of 5792 calls.
Execution time mean : 18.073864 µs
Execution time std-deviation : 623.107379 ns
Execution time lower quantile : 17.421242 µs ( 2.5%)
Execution time upper quantile : 18.715025 µs (97.5%)
Overhead used : 8.800684 ns
nil
```The vast proportion of the runtime cost is the iteration of HTML data structure and fragment writes.
### It's All Interned
Keywords and Strings are interned objects. Therefore the cost of allocating HTML vectors is mostly the cost of allocation vectors, and allocating vectors is really fast.
# License
Released under the MIT License.