{"id":28112538,"url":"https://github.com/xmlet/HtmlFlow","last_synced_at":"2025-05-14T05:04:17.599Z","repository":{"id":2863476,"uuid":"3868521","full_name":"xmlet/HtmlFlow","owner":"xmlet","description":"HtmlFlow Java DSL to write typesafe HTML","archived":false,"fork":false,"pushed_at":"2025-01-27T11:43:03.000Z","size":3884,"stargazers_count":164,"open_issues_count":12,"forks_count":31,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-05-08T18:39:22.779Z","etag":null,"topics":["dsl","fluent-api","java","typesafe"],"latest_commit_sha":null,"homepage":"https://htmlflow.org/","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xmlet.png","metadata":{"files":{"readme":"Readme.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"fmcarvalho","patreon":null,"open_collective":"htmlflow","ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"lfx_crowdfunding":null,"custom":null}},"created_at":"2012-03-29T17:30:08.000Z","updated_at":"2025-04-03T17:59:51.000Z","dependencies_parsed_at":"2023-11-27T14:42:10.657Z","dependency_job_id":"3bbf8e71-6bdc-418a-abae-f4e8d02b1ab8","html_url":"https://github.com/xmlet/HtmlFlow","commit_stats":{"total_commits":425,"total_committers":13,"mean_commits":32.69230769230769,"dds":0.5976470588235294,"last_synced_commit":"81a65879b421c2ad1b70e2d2c7423c89d30ca565"},"previous_names":["fmcarvalho/htmlflow"],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xmlet%2FHtmlFlow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xmlet%2FHtmlFlow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xmlet%2FHtmlFlow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xmlet%2FHtmlFlow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xmlet","download_url":"https://codeload.github.com/xmlet/HtmlFlow/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253873610,"owners_count":21977134,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dsl","fluent-api","java","typesafe"],"created_at":"2025-05-14T05:01:12.244Z","updated_at":"2025-05-14T05:04:17.576Z","avatar_url":"https://github.com/xmlet.png","language":"Java","readme":"# HtmlFlow\n\n[![Build Status](https://sonarcloud.io/api/project_badges/measure?project=com.github.xmlet%3Ahtmlflow\u0026metric=alert_status)](https://sonarcloud.io/dashboard?id=com.github.xmlet%3Ahtmlflow)\n[![Maven Central Version](https://img.shields.io/maven-central/v/com.github.xmlet/htmlflow)](https://central.sonatype.com/artifact/com.github.xmlet/htmlflow)\n[![Coverage Status](https://sonarcloud.io/api/project_badges/measure?project=com.github.xmlet%3Ahtmlflow\u0026metric=coverage)](https://sonarcloud.io/component_measures?id=com.github.xmlet%3Ahtmlflow\u0026metric=Coverage)\n[![javadoc HtmlApiFaster](https://img.shields.io/badge/javadocs-HtmlApiFaster-blue)](https://javadoc.io/doc/com.github.xmlet/htmlApiFaster/latest/org/xmlet/htmlapifaster/package-summary.html)\n[![javadoc HtmlFlow](https://img.shields.io/badge/javadocs-HtmlFlow-blue)](https://javadoc.io/doc/com.github.xmlet/htmlflow)\n\n[![Petclinic Sample](https://img.shields.io/badge/petclinic-Spring%20boot%20sample%20with%20HtmlFlow-blue)](https://github.com/xmlet/spring-petclinic)\n\n[**HtmlFlow**](https://htmlflow.org/) is a Java DSL to write **typesafe HTML**\nin a fluent style, in both **Java** or **Kotlin** (for Kotlin check the [examples](https://htmlflow.org/features#data-binding))\nYou may use the utility `Flowifier.fromHtml(String html)` if you need the HtmlFlow definition for an\nexisting HTML source:\n\n\u003ctable class=\"table\"\u003e\n    \u003ctr\u003e\n        \u003ctd colspan=\"2\" align=\"center\"\u003e\n            \u003cem\u003eoutput\u003c/em\u003e\u0026nbsp \u0026#8628\n        \u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr class=\"row\"\u003e\n        \u003ctd\u003e\n\n```java\nHtmlFlow\n  .doc(System.out)\n    .html() // HtmlPage\n      .head()\n        .title().text(\"HtmlFlow\").__()\n      .__() // head\n      .body()\n        .div().attrClass(\"container\")\n          .h1().text(\"My first page with HtmlFlow\").__()\n          .img().attrSrc(\"http://bit.ly/2MoHwrU\").__()\n          .p().text(\"Typesafe is awesome! :-)\").__()\n        .__() // div\n      .__() // body\n    .__(); // html\n```\n\n\u003c/td\u003e\n\u003ctd\u003e\n\n```html\n\n\n\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003ctitle\u003eHtmlFlow\u003c/title\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003cdiv class=\"container\"\u003e\n      \u003ch1\u003eMy first page with HtmlFlow\u003c/h1\u003e\n      \u003cimg src=\"http://bit.ly/2MoHwrU\"\u003e\n      \u003cp\u003eTypesafe is awesome! :-)\u003c/p\u003e\n    \u003c/div\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n\n```\n\n\u003c/td\u003e\n\u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd colspan=\"2\" align=\"center\"\u003e\n            \u0026#8632 \u0026nbsp\u003ccode\u003eFlowifier.fromHtml(\"...\")\u003c/code\u003e\n        \u003c/td\u003e\n    \u003c/tr\u003e\n\u003c/table\u003e\n\n**NOTICE** for those migrating from legacy API \u003c= 3.x read next about\n[Migrating from HtmlFlow 3.x to 4.x](https://htmlflow.org/2023-01-31-Release-4.0.html).\n\nHtmlFlow is the **most performant** engine among state of the art template\nengines like Velocity, Thymleaf, Mustache, etc and other DSL libraries for HTML such\nas j2Html and KotlinX Html.\nCheck out the performance results in the most popular benchmarks at\n[spring-comparing-template-engines](https://github.com/jreijn/spring-comparing-template-engines#benchmarks-102019)\nand our fork of\n[xmlet/template-benchmark](https://github.com/xmlet/template-benchmark).\n\n[xmlet/spring-petclinic](https://github.com/xmlet/spring-petclinic) provides an implementation of the Spring-based\npetclinic with HtmlFlow [views](https://github.com/xmlet/spring-petclinic/tree/master/src/main/java/org/springframework/samples/petclinic/views).\n\n## Why another templating engine ?\n\nEvery general purpose language has its own [_template engine_](https://en.wikipedia.org/wiki/Template_processor). \nJava has [several](https://en.wikipedia.org/wiki/Comparison_of_web_template_engines).\nMost of the time, templates are defined in a new templating language\n(i.e. [_external DSL_](https://en.wikipedia.org/wiki/Domain-specific_language#External_and_Embedded_Domain_Specific_Languages)).\nTo allow template engines to produce a _view_ based on a template, they generally use\nthe concept of [_model_](https://en.wikipedia.org/wiki/Data_model).\n\nOne of the problems of this technic is that you will end up with a template that\nwon't be type checked.\nSo if you have a typo inside your template, the compiler won't be able to help\nyou before the template is rendered.\n\nHtmlFlow took a different approach. Templates are expressed in an\n[_internal DSL_](https://en.wikipedia.org/wiki/Domain-specific_language#External_and_Embedded_Domain_Specific_Languages).\nYou will write normal Java code to produce your template. \nSo, the full Java toolchain is at your disposal for templating. \nPut it simply, HtmlFlow templates are essentially plain Java functions.\n\nHtmlFlow is not the only one using this approach. But it's the fastest one.\nBonus points it also produces only valid HTML according to HTML 5.2.\n\n## Table of Contents\n\n* [Installation](#installation)\n* [Core Concepts](#core-concepts)\n* [Data Binding](#data-binding)\n* [If/else](#ifelse)\n* [Loops](#loops)\n* [Binding to Asynchronous data models](#binding-to-asynchronous-data-models)\n* [Layout and partial views (aka _fragments_)](https://htmlflow.org/features#layout-and-partial-views-aka-fragments)\n* [Legacy API 3.x](https://htmlflow.org/features_version3)\n* [Changelog](https://htmlflow.org/news)\n* [References](#references)\n* [License](#license)\n* [About](#about)\n\n## Installation\n\nFirst, in order to include it to your Gradle project, simply add the following dependency,\nor use any other form provided in [Maven Central Repository](https://search.maven.org/artifact/com.github.xmlet/htmlflow/4.7/jar):\n\n```groovy\nimplementation 'com.github.xmlet:htmlflow:4.7'\n```\n\nYou can also download the artifact directly from [Maven\nCentral Repository](https://repo1.maven.org/maven2/com/github/xmlet/htmlflow)\n\n## Core Concepts\n\nHtmlFlow builders:\n* element builders (such as `body()`, `div()`, `p()`, etc) return the **created element**\n* `text()` and `raw()` return the **parent element** (e.g. `.h1().text(\"...\")` returns the `H1` parent). `.text()`escapes HTML, while `raw()` doesn't.\n* attribute builders - `attr\u003cattribute name\u003e()` - return their parent (e.g. `.img().attrSrc(\"...\")` returns the `Img`).\n* `__()` or `l` in Kotlin, returns the **parent element** and **emits the end tag** of an element.\n\nHtmlFlow provides both an **_eager_** and a **_lazy_** approach for building HTML.\nThis allows the `Appendable` to be provided either _beforehand_ or _later_ when\nthe view is rendered.\nThe `doc()` and `view()` factory methods follow each of these approaches:\n* **eager**:\n  ```java\n  HtmlFlow.doc(System.out).html().body().h1().text(\"Welcome\").__().table().tr()...\n  ```\n* **eager in Kotlin**:\n  ```kotlin\n  System.out.doc { html { body { h1.text(\"Welcome\").l.table { tr {...} } } } }\n  ```\n* **lazy**:\n  ```java\n  var view = HtmlFlow.\u003cModel\u003eview(page -\u003e page.html().body().text(\"Welcome\").__().table().tr()...)\n  ```\n* **lazy in Kotlin**:\n  ```kotlin\n  val view = view\u003cModel\u003e { html { body { h1.text(\"Welcome\").l.table { tr {...} } } } }\n  ```\n\nAn `HtmlView` is more **performant** than an `HtmlDoc` when we need to bind\nthe same template with different data **models**.\nIn this scenario, **static HTML blocks are resolved only once**, on `HtmlView` instantiation.\n\nGiven an `HtmlView` instance, e.g. `view`, we can render the HTML using one of the\nfollowing approaches:\n* `String html = view.render(tracks)`\n* `view.setOut(System.out).write(tracks)`\n\nThe `setOut()` method accepts any kind of `Appendable` object.\n\n\nIn the following examples, we showcase using only `HtmlDoc`.\nYou can find the equivalent `HtmlView` definition on [htmlflow.org](https://htmlflow.org/features#data-binding) \n\n## Data Binding\n\n_Web templates_ in HtmlFlow are defined using functions (or methods in Java). The\n**model** (or _context object_) may be passed as arguments to such functions.\nNext, we have an example of a dynamic web page binding to a `Track` object.\n\n```java\nvoid trackDoc(Appendable out, Track track) {\n  HtmlFlow.doc(out)\n  .html()\n    .body()\n      .ul()\n        .li()\n          .of((li) -\u003e li\n            .text(format(\"Artist: %s\", track.getArtist())))\n          .__() // li\n        .li()\n          .of((li) -\u003e li\n            .text(format(\"Track: %s\", track.getName())))\n        .__() // li\n      .__() // ul\n    .__() // body\n  .__(); // html\n}\n...\ntrackDoc(System.out, new Track(\"David Bowie\", \"Space Oddity\"));\n```\n\n[trackView](https://htmlflow.org/features#data-binding) equivalent definition to `trackDoc`. \n\nThe `of()` and `dynamic()` builders in `HtmlDoc` and `HtmlView`, respectively,\nare utilized to chain Java code in the definition of web templates:\n\n* `of(Consumer\u003cE\u003e cons)` returns the same element `E`, where `E` is the parent HTML element.\n* `dynamic(BiConsumer\u003cE, M\u003e cons)` - similar to `.of()` but the consumer receives an additional argument `M` (model).\n\n## If/else\n\nRegarding the previous template of `trackDoc` or `trackView`, consider, for\nexample, that you would like to display the **year of the artist's death** for cases\nwhere the artist has already passed away.\nConsidering that `Track` has a property `diedDate` of type `LocalDate`, we can interleave\nthe following HtmlFlow snippet within the `ul` to achieve this purpose:\n\n```java\nvoid trackDoc(Appendable out, Track track) {\n  ...\n    .ul()\n      ...\n      .of(ul -\u003e {\n        if(track.getDiedDate() != null)\n          ul.li().text(format(\"Died in %d\", track.getDiedDate().getYear())).__();\n      })\n      ...\n}\n```\n\n[trackView](https://htmlflow.org/features#ifelse) equivalent definition to `trackDoc`.\n\n## Loops\n\nYou can utilize any Java loop statement in your web template definition. Next,\nwe present an example that takes advantage of the `forEach` loop method of\n`Iterable`:\n\n```java\nvoid playlistDoc(Appendable out, List\u003cTrack\u003e tracks) {\n  HtmlFlow.doc(out)\n    .html()\n      .body()\n        .table()\n          .tr()\n            .th().text(\"Artist\").__()\n            .th().text(\"Track\").__()\n          .__() // tr\n          .of(table -\u003e tracks.forEach( trk -\u003e\n            table\n              .tr()\n                .td().text(trk.getArtist()).__()\n                .td().text(trk.getName()).__()\n              .__() // tr\n          ))\n        .__() // table\n      .__() // body\n    .__(); // html\n}\n```\n\n[playlistView](https://htmlflow.org/features#loops) equivalent definition to `playlistDoc`.\n\n## Binding to Asynchronous data models\n\nTo ensure well-formed HTML, HtmlFlow needs to observe the completion of\nasynchronous models. Otherwise, text or HTML elements following an asynchronous\nmodel binding may be emitted before the HTML resulting from the asynchronous\nmodel.\n\nTo bind an asynchronous model, one should use the builder\n`.await(parent, model, onCompletion) -\u003e ...)`\nwhere the `onCompletion` callback signals to HtmlFlow that it can proceed to the\nnext continuation.\n\nNext we present the asynchronous version of the playlist web template.\nInstead of a `List\u003cTrack\u003e` we are binding to a [Flux](https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Flux.html),\nwhich is a Reactive Streams [`Publisher`](https://www.reactive-streams.org/reactive-streams-1.0.3-javadoc/org/reactivestreams/Publisher.html?is-external=true) with reactive operators that emits 0 to N elements.\n\n\n```java\nHtmlViewAsync\u003cFlux\u003cTrack\u003e\u003e playlistView = HtmlFlow.viewAsync(view -\u003e view\n  .html()\n    .body()\n      .table()\n        .tr()\n          .th().text(\"Artist\").__()\n          .th().text(\"Track\").__()\n        .__() // tr\n        .\u003cFlux\u003cTrack\u003e\u003eawait((table, tracks, onCompletion) -\u003e tracks\n          .doOnComplete(onCompletion::finish)\n          .doOnNext( trk -\u003e\n            table\n              .tr()\n                .td().text(trk.getArtist()).__()\n                .td().text(trk.getName()).__()\n              .__()\n        ))\n      .__() // table\n    .__() // body\n  .__() // html\n);\n```\n\nHtmlFlow _await_ feature works regardless the type of asynchronous model and can be used with\nany kind of asynchronous API.\n\n## References\n\n* 2024, [Progressive Server-Side Rendering with Suspendable Web Templates](https://gamboa.pt/img/my-papers/wise-2024-pssr-async-await.pdf), 25th edition of [WISE](https://wise2024-qatar.com/), Doha, Qatar, ([slides](https://gamboa.pt/img/my-papers/wise-2024-slides.pdf)).\n* 2023,\n  [Enhancing SSR in Low-Thread Web Servers](https://www.scitepress.org/Link.aspx?doi=10.5220/0012165300003584),\n  [19th WebIst](http://www.webist.org/?y=2023) conference, 2013, Rome - This paper highlights the HtmlFlow templating\n  approach that embraces any asynchronous AP I (e.g., Publisher, promises,\n  suspend functions, flow, etc.) and allows for multiple asynchronous data\n  sources ([slides](https://gamboa.pt/img/my-papers/webist-2023-slides.pdf)).\n* 2020, [Text Web Templates Considered Harmful](https://link.springer.com/chapter/10.1007/978-3-030-61750-9_4),\n  Part of the Lecture Notes in Business Information Processing book series (LNBIP, volume 399).\n  This paper shows how a DSL for HTML (such as HtmlFlow or Kotlinx.Html)\n  provides unopinionated web templates with boundless resolving features only\n  ruled by the host programming language (such as Java, Kotlin or JavaScript).\n* 2019, [HoT: Unleash Web Views with Higher-order\n  Templates](https://www.scitepress.org/Link.aspx?doi=10.5220/0008167701180129),\n  [15th WebIst](http://www.webist.org/?y=2019) conference, 2019, Viena - This\n  paper highlights the compositional nature of HtmlFlow to compose templates\n  through higher-order functions. \n* 2018, [Domain Specific Language generation based on a XML\n  Schema](https://www.slideshare.net/LuisDuarte105/domain-specific-language-generation-based-on-a-xml-schema-208756986).\n  Slides of the MsC thesis presentation of Luís Duarte.\n* 2018, [Modern Type-Safe Template\n  Engines](https://dzone.com/articles/modern-type-safe-template-engines) - You\n  can find more details in this DZone article about performance comparison.\n\n## License\n\n[MIT](https://github.com/xmlet/HtmlFlow/blob/master/LICENSE)\n\n## About\n\nHtmlFlow was created by [Miguel Gamboa](http://gamboa.pt/) (aka\n[fmcarvalho](https://github.com/fmcarvalho/)), an assistant professor of\n[Computer Science and\nEngineering](https://www.isel.pt/en/courses/bsc-degree/computer-science-and-engineering)\nof [ISEL](https://www.isel.pt/en/), [Polytechnic Institute of\nLisbon](https://www.ipl.pt/en).\n","funding_links":["https://github.com/sponsors/fmcarvalho","https://opencollective.com/htmlflow"],"categories":["模板引擎"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxmlet%2FHtmlFlow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxmlet%2FHtmlFlow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxmlet%2FHtmlFlow/lists"}