From 2ec7bb61d297fa3bf4954721255694669de11eb0 Mon Sep 17 00:00:00 2001 From: onionpancakes <639985+onionpancakes@users.noreply.github.com> Date: Tue, 20 Feb 2024 00:50:50 -0800 Subject: [PATCH] added readme section on compiling --- README.md | 183 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 181 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7141502..e33c783 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ HTML5 serialization for Clojure. Renders [Hiccup](https://github.com/weavejester/hiccup/) style HTML vectors to strings. -Highly optimized runtime serialization without macros. See [Performance](#performance). +Highly optimized runtime serialization without macros. See [Runtime Performance](#runtime-performance). + +Even faster serialization with compiling macros. See [Compiling Elements](#compiling-elements). # Status @@ -399,7 +401,7 @@ Use the `nbsp` constant. ;; "
foo bar
" ``` -# Performance +# Runtime Performance At this time, benchmarks shows Chassis to be ~50% to +100% faster when compared to other Clojure HTML templating libraries. See bench results in the resource folder. @@ -490,6 +492,183 @@ nil 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. +# 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 return compiled versions of the HTML tree. Use them to compile elements before passing them 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 "
"]] +``` + + + +## 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 (macroexpand-1 + '(cc/compile [:div (:body data)])))) + +;; Results in: +[(let* + [attrs13475 (:body data)] + (if ;; Check if 2nd item is attrs map at runtime. + (dev.onionpancakes.chassis.core/attrs? attrs13475) + (dev.onionpancakes.chassis.core/->OpeningTag + nil + :div + nil + nil + attrs13475) + [#object[dev.onionpancakes.chassis.core.OpeningTag 0x34b8fe4b "
"] + attrs13475])) + #object[dev.onionpancakes.chassis.core.RawString 0x1b09ab08 "
"]] +``` + +### 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"])) +``` + # License Released under the MIT License. \ No newline at end of file