├── .github └── workflows │ └── clojars.yml ├── .gitignore ├── LICENSE ├── bb.edn ├── build.clj ├── deps.edn ├── perf ├── huff │ └── perf.clj └── timings.md ├── perf_resources ├── big_page.edn └── medium_page.edn ├── readme.md ├── src ├── huff │ └── core.clj └── huff2 │ ├── core.clj │ └── extension.clj └── test └── huff ├── core2_test.clj ├── core_test.clj ├── extension_test.clj ├── hiccup22_test.clj └── hiccup2_test.clj /.github/workflows/clojars.yml: -------------------------------------------------------------------------------- 1 | name: Clojure CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - '*' 9 | workflow_dispatch: 10 | 11 | jobs: 12 | deploy: 13 | runs-on: ubuntu-latest 14 | if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4.1.1 19 | with: 20 | fetch-depth: 0 21 | 22 | - name: Prepare java 23 | uses: actions/setup-java@v4.2.0 24 | with: 25 | java-version: '21' 26 | distribution: 'temurin' 27 | 28 | - name: Install clojure tools 29 | uses: DeLaGuardo/setup-clojure@12.5 30 | with: 31 | cli: latest 32 | bb: latest 33 | 34 | - name: Run tests 35 | run: bb tests 36 | 37 | - name: Build jar 38 | run: bb uber 39 | 40 | - name: Deploy to clojars 41 | env: 42 | CLOJARS_USERNAME: ${{ secrets.CLOJARS_USERNAME }} 43 | CLOJARS_PASSWORD: ${{ secrets.CLOJARS_PASSWORD }} 44 | run: bb deploy 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .clj-kondo/.cache/v1/clj/cheshire.core.transit.json 2 | .clj-kondo/.cache/v1/lock 3 | .calva/ 4 | .clj-kondo/ 5 | .cpcache/ 6 | .lsp/ 7 | target/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 2.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 4 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION 5 | OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial content 12 | Distributed under this Agreement, and 13 | 14 | b) in the case of each subsequent Contributor: 15 | i) changes to the Program, and 16 | ii) additions to the Program; 17 | where such changes and/or additions to the Program originate from 18 | and are Distributed by that particular Contributor. A Contribution 19 | "originates" from a Contributor if it was added to the Program by 20 | such Contributor itself or anyone acting on such Contributor's behalf. 21 | Contributions do not include changes or additions to the Program that 22 | are not Modified Works. 23 | 24 | "Contributor" means any person or entity that Distributes the Program. 25 | 26 | "Licensed Patents" mean patent claims licensable by a Contributor which 27 | are necessarily infringed by the use or sale of its Contribution alone 28 | or when combined with the Program. 29 | 30 | "Program" means the Contributions Distributed in accordance with this 31 | Agreement. 32 | 33 | "Recipient" means anyone who receives the Program under this Agreement 34 | or any Secondary License (as applicable), including Contributors. 35 | 36 | "Derivative Works" shall mean any work, whether in Source Code or other 37 | form, that is based on (or derived from) the Program and for which the 38 | editorial revisions, annotations, elaborations, or other modifications 39 | represent, as a whole, an original work of authorship. 40 | 41 | "Modified Works" shall mean any work in Source Code or other form that 42 | results from an addition to, deletion from, or modification of the 43 | contents of the Program, including, for purposes of clarity any new file 44 | in Source Code form that contains any contents of the Program. Modified 45 | Works shall not include works that contain only declarations, 46 | interfaces, types, classes, structures, or files of the Program solely 47 | in each case in order to link to, bind by name, or subclass the Program 48 | or Modified Works thereof. 49 | 50 | "Distribute" means the acts of a) distributing or b) making available 51 | in any manner that enables the transfer of a copy. 52 | 53 | "Source Code" means the form of a Program preferred for making 54 | modifications, including but not limited to software source code, 55 | documentation source, and configuration files. 56 | 57 | "Secondary License" means either the GNU General Public License, 58 | Version 2.0, or any later versions of that license, including any 59 | exceptions or additional permissions as identified by the initial 60 | Contributor. 61 | 62 | 2. GRANT OF RIGHTS 63 | 64 | a) Subject to the terms of this Agreement, each Contributor hereby 65 | grants Recipient a non-exclusive, worldwide, royalty-free copyright 66 | license to reproduce, prepare Derivative Works of, publicly display, 67 | publicly perform, Distribute and sublicense the Contribution of such 68 | Contributor, if any, and such Derivative Works. 69 | 70 | b) Subject to the terms of this Agreement, each Contributor hereby 71 | grants Recipient a non-exclusive, worldwide, royalty-free patent 72 | license under Licensed Patents to make, use, sell, offer to sell, 73 | import and otherwise transfer the Contribution of such Contributor, 74 | if any, in Source Code or other form. This patent license shall 75 | apply to the combination of the Contribution and the Program if, at 76 | the time the Contribution is added by the Contributor, such addition 77 | of the Contribution causes such combination to be covered by the 78 | Licensed Patents. The patent license shall not apply to any other 79 | combinations which include the Contribution. No hardware per se is 80 | licensed hereunder. 81 | 82 | c) Recipient understands that although each Contributor grants the 83 | licenses to its Contributions set forth herein, no assurances are 84 | provided by any Contributor that the Program does not infringe the 85 | patent or other intellectual property rights of any other entity. 86 | Each Contributor disclaims any liability to Recipient for claims 87 | brought by any other entity based on infringement of intellectual 88 | property rights or otherwise. As a condition to exercising the 89 | rights and licenses granted hereunder, each Recipient hereby 90 | assumes sole responsibility to secure any other intellectual 91 | property rights needed, if any. For example, if a third party 92 | patent license is required to allow Recipient to Distribute the 93 | Program, it is Recipient's responsibility to acquire that license 94 | before distributing the Program. 95 | 96 | d) Each Contributor represents that to its knowledge it has 97 | sufficient copyright rights in its Contribution, if any, to grant 98 | the copyright license set forth in this Agreement. 99 | 100 | e) Notwithstanding the terms of any Secondary License, no 101 | Contributor makes additional grants to any Recipient (other than 102 | those set forth in this Agreement) as a result of such Recipient's 103 | receipt of the Program under the terms of a Secondary License 104 | (if permitted under the terms of Section 3). 105 | 106 | 3. REQUIREMENTS 107 | 108 | 3.1 If a Contributor Distributes the Program in any form, then: 109 | 110 | a) the Program must also be made available as Source Code, in 111 | accordance with section 3.2, and the Contributor must accompany 112 | the Program with a statement that the Source Code for the Program 113 | is available under this Agreement, and informs Recipients how to 114 | obtain it in a reasonable manner on or through a medium customarily 115 | used for software exchange; and 116 | 117 | b) the Contributor may Distribute the Program under a license 118 | different than this Agreement, provided that such license: 119 | i) effectively disclaims on behalf of all other Contributors all 120 | warranties and conditions, express and implied, including 121 | warranties or conditions of title and non-infringement, and 122 | implied warranties or conditions of merchantability and fitness 123 | for a particular purpose; 124 | 125 | ii) effectively excludes on behalf of all other Contributors all 126 | liability for damages, including direct, indirect, special, 127 | incidental and consequential damages, such as lost profits; 128 | 129 | iii) does not attempt to limit or alter the recipients' rights 130 | in the Source Code under section 3.2; and 131 | 132 | iv) requires any subsequent distribution of the Program by any 133 | party to be under a license that satisfies the requirements 134 | of this section 3. 135 | 136 | 3.2 When the Program is Distributed as Source Code: 137 | 138 | a) it must be made available under this Agreement, or if the 139 | Program (i) is combined with other material in a separate file or 140 | files made available under a Secondary License, and (ii) the initial 141 | Contributor attached to the Source Code the notice described in 142 | Exhibit A of this Agreement, then the Program may be made available 143 | under the terms of such Secondary Licenses, and 144 | 145 | b) a copy of this Agreement must be included with each copy of 146 | the Program. 147 | 148 | 3.3 Contributors may not remove or alter any copyright, patent, 149 | trademark, attribution notices, disclaimers of warranty, or limitations 150 | of liability ("notices") contained within the Program from any copy of 151 | the Program which they Distribute, provided that Contributors may add 152 | their own appropriate notices. 153 | 154 | 4. COMMERCIAL DISTRIBUTION 155 | 156 | Commercial distributors of software may accept certain responsibilities 157 | with respect to end users, business partners and the like. While this 158 | license is intended to facilitate the commercial use of the Program, 159 | the Contributor who includes the Program in a commercial product 160 | offering should do so in a manner which does not create potential 161 | liability for other Contributors. Therefore, if a Contributor includes 162 | the Program in a commercial product offering, such Contributor 163 | ("Commercial Contributor") hereby agrees to defend and indemnify every 164 | other Contributor ("Indemnified Contributor") against any losses, 165 | damages and costs (collectively "Losses") arising from claims, lawsuits 166 | and other legal actions brought by a third party against the Indemnified 167 | Contributor to the extent caused by the acts or omissions of such 168 | Commercial Contributor in connection with its distribution of the Program 169 | in a commercial product offering. The obligations in this section do not 170 | apply to any claims or Losses relating to any actual or alleged 171 | intellectual property infringement. In order to qualify, an Indemnified 172 | Contributor must: a) promptly notify the Commercial Contributor in 173 | writing of such claim, and b) allow the Commercial Contributor to control, 174 | and cooperate with the Commercial Contributor in, the defense and any 175 | related settlement negotiations. The Indemnified Contributor may 176 | participate in any such claim at its own expense. 177 | 178 | For example, a Contributor might include the Program in a commercial 179 | product offering, Product X. That Contributor is then a Commercial 180 | Contributor. If that Commercial Contributor then makes performance 181 | claims, or offers warranties related to Product X, those performance 182 | claims and warranties are such Commercial Contributor's responsibility 183 | alone. Under this section, the Commercial Contributor would have to 184 | defend claims against the other Contributors related to those performance 185 | claims and warranties, and if a court requires any other Contributor to 186 | pay any damages as a result, the Commercial Contributor must pay 187 | those damages. 188 | 189 | 5. NO WARRANTY 190 | 191 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 192 | PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" 193 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 194 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF 195 | TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 196 | PURPOSE. Each Recipient is solely responsible for determining the 197 | appropriateness of using and distributing the Program and assumes all 198 | risks associated with its exercise of rights under this Agreement, 199 | including but not limited to the risks and costs of program errors, 200 | compliance with applicable laws, damage to or loss of data, programs 201 | or equipment, and unavailability or interruption of operations. 202 | 203 | 6. DISCLAIMER OF LIABILITY 204 | 205 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 206 | PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS 207 | SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 208 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST 209 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 210 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 211 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 212 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE 213 | POSSIBILITY OF SUCH DAMAGES. 214 | 215 | 7. GENERAL 216 | 217 | If any provision of this Agreement is invalid or unenforceable under 218 | applicable law, it shall not affect the validity or enforceability of 219 | the remainder of the terms of this Agreement, and without further 220 | action by the parties hereto, such provision shall be reformed to the 221 | minimum extent necessary to make such provision valid and enforceable. 222 | 223 | If Recipient institutes patent litigation against any entity 224 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 225 | Program itself (excluding combinations of the Program with other software 226 | or hardware) infringes such Recipient's patent(s), then such Recipient's 227 | rights granted under Section 2(b) shall terminate as of the date such 228 | litigation is filed. 229 | 230 | All Recipient's rights under this Agreement shall terminate if it 231 | fails to comply with any of the material terms or conditions of this 232 | Agreement and does not cure such failure in a reasonable period of 233 | time after becoming aware of such noncompliance. If all Recipient's 234 | rights under this Agreement terminate, Recipient agrees to cease use 235 | and distribution of the Program as soon as reasonably practicable. 236 | However, Recipient's obligations under this Agreement and any licenses 237 | granted by Recipient relating to the Program shall continue and survive. 238 | 239 | Everyone is permitted to copy and distribute copies of this Agreement, 240 | but in order to avoid inconsistency the Agreement is copyrighted and 241 | may only be modified in the following manner. The Agreement Steward 242 | reserves the right to publish new versions (including revisions) of 243 | this Agreement from time to time. No one other than the Agreement 244 | Steward has the right to modify this Agreement. The Eclipse Foundation 245 | is the initial Agreement Steward. The Eclipse Foundation may assign the 246 | responsibility to serve as the Agreement Steward to a suitable separate 247 | entity. Each new version of the Agreement will be given a distinguishing 248 | version number. The Program (including Contributions) may always be 249 | Distributed subject to the version of the Agreement under which it was 250 | received. In addition, after a new version of the Agreement is published, 251 | Contributor may elect to Distribute the Program (including its 252 | Contributions) under the new version. 253 | 254 | Except as expressly stated in Sections 2(a) and 2(b) above, Recipient 255 | receives no rights or licenses to the intellectual property of any 256 | Contributor under this Agreement, whether expressly, by implication, 257 | estoppel or otherwise. All rights in the Program not expressly granted 258 | under this Agreement are reserved. Nothing in this Agreement is intended 259 | to be enforceable by any entity that is not a Contributor or Recipient. 260 | No third-party beneficiary rights are created under this Agreement. 261 | 262 | Exhibit A - Form of Secondary Licenses Notice 263 | 264 | "This Source Code may also be made available under the following 265 | Secondary Licenses when the conditions for such availability set forth 266 | in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), 267 | version(s), and exceptions or additional permissions here}." 268 | 269 | Simply including a copy of this Agreement, including this Exhibit A 270 | is not sufficient to license the Source Code under Secondary Licenses. 271 | 272 | If it is not possible or desirable to put the notice in a particular 273 | file, then You may include the notice in a location (such as a LICENSE 274 | file in a relevant directory) where a recipient would be likely to 275 | look for such a notice. 276 | 277 | You may add additional accurate notices of copyright ownership. 278 | -------------------------------------------------------------------------------- /bb.edn: -------------------------------------------------------------------------------- 1 | {:deps {com.escherize/huff {:local/root "."}} 2 | :tasks 3 | {clean {:doc "Clean up" 4 | :task (do 5 | (println "Cleaning up...") 6 | (clojure "-T:build clean"))} 7 | uber {:doc "Build uberjar" 8 | :task (do 9 | (run 'clean) 10 | (println "Building uberjar...") 11 | (clojure "-T:build jar"))} 12 | 13 | deploy {:doc "Deploy to Clojars" 14 | :task (do 15 | (println "Deploying to clojars") 16 | (clojure "-T:build deploy"))} 17 | 18 | -bb-tests {:requires ([clojure.string :as str] 19 | [clojure.test :as t] 20 | [babashka.classpath :as cp] 21 | [babashka.fs :as fs]) 22 | :task (do 23 | (cp/add-classpath "src:test") 24 | (let [test-nss (for [test-file (fs/glob "test" "**.clj") 25 | :let [test-ns (-> test-file 26 | str 27 | (->> (re-matches (re-pattern "test/(.*).clj"))) 28 | second 29 | (str/replace "_" "-" ) 30 | (str/replace "/" ".") 31 | symbol)]] 32 | (do (require test-ns) test-ns)) 33 | results (apply t/run-tests test-nss)] 34 | (when (pos? (+ (:fail results) (:error results))) 35 | (System/exit 1))))} 36 | tests {:doc "Run tests in bb and Clj." 37 | :task (do 38 | (println "+---------------------------+") 39 | (println "| Running tests in Babashka |") 40 | (println "+---------------------------+") 41 | (run '-bb-tests) 42 | (println "+--------------------------+") 43 | (println "| Running tests in Clojure |") 44 | (println "+--------------------------+") 45 | (clojure "-M:test"))}}} 46 | -------------------------------------------------------------------------------- /build.clj: -------------------------------------------------------------------------------- 1 | (ns build 2 | (:require 3 | [clojure.java.shell :refer [sh]] 4 | [clojure.string :as str] 5 | [clojure.tools.build.api :as b] 6 | [org.corfield.build :as bb])) 7 | 8 | (defn semver-sort [semver-str] (->> (str/split semver-str #"\.") (mapv parse-long))) 9 | 10 | (defn git-tag 11 | [] 12 | (let [tag (->> (sh "git" "tag") :out str/split-lines (sort-by semver-sort) last)] 13 | (if (seq tag) tag "0.1-SNAPSHOT"))) 14 | 15 | 16 | (def lib 'io.github.escherize/huff) 17 | (def main 'huff.core) 18 | (def version (git-tag)) 19 | (def class-dir "target/classes") 20 | (def basis (b/create-basis {:project "deps.edn"})) 21 | (def jar-file (format "target/%s-%s.jar" (name lib) version)) 22 | 23 | (defn clean 24 | [_] 25 | (b/delete {:path "pom.xml"}) 26 | (b/delete {:path "target"})) 27 | 28 | (def pom-template 29 | [[:description "A library for cozy and delightful html generation."] 30 | [:url "https://github.com/escherize/huff/"] 31 | [:licenses 32 | [:license 33 | [:name "Eclipse Public License"] 34 | [:url "http://www.eclipse.org/legal/epl-v10.html"]]] 35 | [:developers 36 | [:developer 37 | [:name "Bryan Maass"]]]]) 38 | 39 | (defn jar 40 | [_] 41 | (println "\nWriting pom.xml...") 42 | (b/write-pom {:basis basis 43 | :class-dir class-dir 44 | :target "target" 45 | :lib lib 46 | :version version 47 | :scm {:url "https://github.com/escherize/huff" 48 | :connection "scm:git:git://github.com/escherize/huff.git" 49 | :developerConnection "scm:git:ssh://git@github.com/escherize/huff.git" 50 | :tag (git-tag)} 51 | :src-dirs ["src"] 52 | :resource-dirs ["resources"] 53 | :pom-data pom-template}) 54 | (println "\nCopying source...") 55 | (b/copy-dir {:src-dirs ["src" "resources"] 56 | :target-dir class-dir}) 57 | (printf "\nBuilding %s...\n" jar-file) 58 | (b/jar {:class-dir class-dir :jar-file jar-file})) 59 | 60 | 61 | (defn deploy 62 | "Deploy the JAR to Clojars." 63 | [{:as opts}] 64 | (-> opts 65 | (assoc :lib lib 66 | :version version 67 | :main main) 68 | (bb/deploy))) 69 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src"] 2 | :deps {metosin/malli {:mvn/version "0.13.0"}} 3 | :aliases {:perf {:extra-paths ["perf" "perf_resources"] 4 | :extra-deps {hiccup/hiccup {:mvn/version "1.0.5"} 5 | com.lambdaisland/hiccup {:mvn/version "0.0.33"} 6 | com.taoensso/tufte {:mvn/version "2.6.3"} 7 | criterium/criterium {:mvn/version "0.4.6"}}} 8 | :build {:ns-default build 9 | :deps {io.github.seancorfield/build-clj {:git/tag "v0.8.3" :git/sha "7ac1f8d"} 10 | io.github.clojure/tools.build {:mvn/version "0.9.6"}}} 11 | :test {:extra-paths ["test"] 12 | :extra-deps {io.github.cognitect-labs/test-runner {:git/tag "v0.5.1" :git/sha "dfb30dd" :git/url "https://github.com/cognitect-labs/test-runner"}} 13 | :main-opts ["-m" "cognitect.test-runner"] 14 | :exec-fn cognitect.test-runner.api/test}}} 15 | -------------------------------------------------------------------------------- /perf/huff/perf.clj: -------------------------------------------------------------------------------- 1 | (ns huff.perf 2 | (:require 3 | [clojure.java.io :as io] 4 | [criterium.core :as bench] 5 | [hiccup.core :as hiccup] 6 | [huff.core :as h] 7 | [taoensso.tufte :as tufte :refer (defnp p profiled profile)])) 8 | 9 | ;; `page.edn` is wikipedia's list of common misconceptions passed through html2hiccup. 10 | 11 | ;; we support fragments: 12 | (defonce big-page (read-string (str "[:<> " (slurp (io/resource "big_page.edn")) "]"))) 13 | (defonce medium-page (read-string (str "[:<> " (slurp (io/resource "medium_page.edn")) "]"))) 14 | 15 | (defn print-bench [] 16 | (println "# Dynamic Performance Testing") 17 | (println "## Benching with big-page ([the wikipedia list of common misconceptions](https://en.wikipedia.org/wiki/List_of_common_misconceptions) as hiccup)") 18 | (println "### hiccup") 19 | (println "```") 20 | (bench/bench (hiccup/html (drop 1 big-page))) 21 | (println "```") 22 | (println "### huff") 23 | (println "```") 24 | (bench/bench (h/html big-page)) 25 | (println "```")(println)(println) 26 | 27 | (println "## Benching with medium_page ([a github Issue page](https://github.com/escherize/huff/issues/8) ~ 40kbp)") 28 | (println "### hiccup") 29 | (println "```") 30 | (bench/bench (hiccup/html (drop 1 medium-page))) 31 | (println "```") 32 | (println "### huff") 33 | (println "```") 34 | (bench/bench (h/html medium-page)) 35 | (println "```")) 36 | 37 | 38 | 39 | 40 | (comment 41 | (tufte/add-basic-println-handler! {}) 42 | (profile {} (h/html big-page))) 43 | -------------------------------------------------------------------------------- /perf/timings.md: -------------------------------------------------------------------------------- 1 | # Performance Testing 2 | ## Benching with big-page ([the wikipedia list of common misconceptions](https://en.wikipedia.org/wiki/List_of_common_misconceptions) as hiccup) 3 | ### hiccup 4 | ``` 5 | Evaluation count : 240 in 60 samples of 4 calls. 6 | Execution time mean : 295.702125 ms 7 | Execution time std-deviation : 5.093515 ms 8 | Execution time lower quantile : 285.061056 ms ( 2.5%) 9 | Execution time upper quantile : 306.661192 ms (97.5%) 10 | Overhead used : 7.091991 ns 11 | 12 | Found 13 outliers in 60 samples (21.6667 %) 13 | low-severe 3 (5.0000 %) 14 | low-mild 6 (10.0000 %) 15 | high-mild 1 (1.6667 %) 16 | high-severe 3 (5.0000 %) 17 | Variance from outliers : 6.2766 % Variance is slightly inflated by outliers 18 | ``` 19 | ### pre-compiled hiccup template 20 | ``` 21 | Evaluation count : 240 in 60 samples of 4 calls. 22 | Execution time mean : 294.781939 ms 23 | Execution time std-deviation : 4.182724 ms 24 | Execution time lower quantile : 291.247774 ms ( 2.5%) 25 | Execution time upper quantile : 302.174457 ms (97.5%) 26 | Overhead used : 7.091991 ns 27 | 28 | Found 2 outliers in 60 samples (3.3333 %) 29 | low-severe 1 (1.6667 %) 30 | low-mild 1 (1.6667 %) 31 | Variance from outliers : 1.6389 % Variance is slightly inflated by outliers 32 | ``` 33 | ### huff 34 | ``` 35 | Evaluation count : 300 in 60 samples of 5 calls. 36 | Execution time mean : 227.239306 ms 37 | Execution time std-deviation : 10.571710 ms 38 | Execution time lower quantile : 217.694693 ms ( 2.5%) 39 | Execution time upper quantile : 253.611046 ms (97.5%) 40 | Overhead used : 7.091991 ns 41 | 42 | Found 11 outliers in 60 samples (18.3333 %) 43 | low-severe 5 (8.3333 %) 44 | low-mild 6 (10.0000 %) 45 | Variance from outliers : 31.9964 % Variance is moderately inflated by outliers 46 | ``` 47 | 48 | 49 | ## Benching with medium_page ([a github Issue page](https://github.com/escherize/huff/issues/8) ~ 40kbp) 50 | ### hiccup 51 | ``` 52 | Evaluation count : 1320 in 60 samples of 22 calls. 53 | Execution time mean : 46.369788 ms 54 | Execution time std-deviation : 764.313065 µs 55 | Execution time lower quantile : 45.722580 ms ( 2.5%) 56 | Execution time upper quantile : 47.554377 ms (97.5%) 57 | Overhead used : 7.091991 ns 58 | 59 | Found 2 outliers in 60 samples (3.3333 %) 60 | low-severe 1 (1.6667 %) 61 | low-mild 1 (1.6667 %) 62 | Variance from outliers : 5.6508 % Variance is slightly inflated by outliers 63 | ``` 64 | ### pre-compiled hiccup template 65 | ``` 66 | Evaluation count : 1320 in 60 samples of 22 calls. 67 | Execution time mean : 47.383825 ms 68 | Execution time std-deviation : 1.103090 ms 69 | Execution time lower quantile : 46.392809 ms ( 2.5%) 70 | Execution time upper quantile : 49.398033 ms (97.5%) 71 | Overhead used : 7.091991 ns 72 | ``` 73 | ### huff 74 | ``` 75 | Evaluation count : 2340 in 60 samples of 39 calls. 76 | Execution time mean : 25.463353 ms 77 | Execution time std-deviation : 554.140695 µs 78 | Execution time lower quantile : 24.811835 ms ( 2.5%) 79 | Execution time upper quantile : 27.012070 ms (97.5%) 80 | Overhead used : 7.091991 ns 81 | 82 | Found 2 outliers in 60 samples (3.3333 %) 83 | low-severe 2 (3.3333 %) 84 | Variance from outliers : 9.4501 % Variance is slightly inflated by outliers 85 | ``` 86 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # huff 2 | 3 | Hiccup in pure Clojure 4 | 5 | ## Usage 6 | 7 | [![Clojars Project](https://img.shields.io/clojars/v/io.github.escherize/huff.svg)](https://clojars.org/io.github.escherize/huff) 8 | 9 | ``` clojure 10 | (require '[huff2.core :as h]) 11 | ``` 12 | 13 | ## Rationale 14 | 15 | When it comes to hiccup libraries, there's a venn-diagram "has ergonomic and modern affordances" and "works on babashka". So [huff](https://github.com/escherize/huff) is my way of saying why not both? 16 | 17 | - [Weavejester's hiccup library](https://github.com/weavejester/hiccup) runs on babashka, but is missing some of the newer features hiccup afficianados have come to demand. 18 | 19 | - [Lambda Island's hiccup](https://github.com/lambdaisland/hiccup) also provides a modern api, but overall I'd still call it a subset of huff's features. 20 | 21 | ## Features 22 | 23 | - 🏭 Use [**functions** as **components**](#use-functons-as-components) 24 | - 🎨 Style maps work as you'd expect `[:div {:style {:font-size 30}}]` 25 | - 🔀 Include classes and ids tags [in any order](#tag-parsing) 26 | - `:div.a#id.b` or `:div.a.c#id` or `:#id.a.c` all work! 27 | - 🔒️ HTML-encoded by default 28 | - 👵 Runs on [babashka](https://github.com/babashka/babashka) (unlike `lambdaisland/hiccup`) 29 | - 🏎 Performance: 22-48% faster than hiccup/hiccup for runtime-generated HTML [without pre-compilation](https://github.com/escherize/huff/issues/8) 30 | - 🙂 Hiccup-style fragments to return multiple forms `(list [:li.a] [:li.b])` 31 | - 🙃 Reagent-style fragments to return multiple forms `[:<> [:li.a] [:li.b]]` 32 | - 🤐 Extreme shorthand syntax `[:. {:color :red}]` `
` 33 | - 🦺 Tested against slightly modified hiccup 2 tests 34 | - *NEW*: 🪗 [Extendable grammar](#extendable-grammar) + custom emitter functions! 35 | - *NEW*: 📦 [raw-string](https://github.com/escherize/huff/issues/5) support 36 | 37 | ### Tag Parsing 38 | 39 | Parse tags for id and class (in any order). 40 | 41 | ```clojure 42 | (h/html [:div.hello#world "!"]) 43 | ;; =>
!
44 | ``` 45 | 46 | #### Nested tag parsing 47 | 48 | ```clojure 49 | (println (h/html [:div.left-aligned>p#user-parent>span {:id "user-name"} "Jason"])) 50 | 51 | ;=>

Jason

52 | ``` 53 | 54 | ### [reagent](https://github.com/reagent-project/reagent)-like fragment tags 55 | 56 | ```clojure 57 | (h/html [:<> [:div "d"] [:<> [:<> [:span "s"]]]]) 58 | ;; => 59 |
d
s 60 | ``` 61 | 62 | This is useful for returning multiple elements from a function: 63 | 64 | ```clojure 65 | (defn twins [x] [:<> 66 | [:div.a x] 67 | [:div.b x]]) 68 | 69 | (h/html [:span.parent [twins "elements"]]) 70 | ;;=> 71 | 72 |
elements
73 |
elements
74 |
75 | 76 | ``` 77 | 78 | Nest and combine them with lists to better convey intent to expand: 79 | 80 | ``` clojure 81 | (h/html 82 | [:ol 83 | [:<> (for [x [1 2]] 84 | [:li>p.green {:id (str "id-" x)} x])]]) 85 | 86 | ;;=> 87 |
    88 |
  1. 89 |

    1

    90 |
  2. 91 |
  3. 92 |

    2

    93 |
  4. 94 |
95 | 96 | ``` 97 | 98 | ### Style map rendering 99 | 100 | ```clojure 101 | (h/html [:div {:style {:border "1px red solid" 102 | :background-color "#ff00ff"}}]) 103 | ;; =>
104 | 105 | (h/html [:. {:style {:width 3}}]) 106 | ;;=>
107 | ``` 108 | 109 | ### Raw HTML tag: 110 | 111 | This is nice if you want to e.g. render markdown in the middle of your hiccup. Note: We disable this by default and it must be manually enabled in the call to `html` or `page`, 112 | 113 | ``` clojure 114 | 115 | (h/html [:hiccup/raw-html "
raw
"]) 116 | ;; =Throws=> :hiccup/raw-html is not allowed. Maybe you meant to set allow-raw to true? 117 | 118 | (h/html {:allow-raw true} [:hiccup/raw-html "
raw
"]) 119 | ;;=>
raw
120 | ``` 121 | 122 | Another nice-to-have is to disallow raw html in un-trusted data getting passed into to the compiler, but being able to do that as a dev. 123 | 124 | ``` clojure 125 | (h/html [:div [:hiccup/raw-html (h/raw-string "