Writing HTML with Racket and X-Expressions (2019)
xy2.dev
xy2.dev
> But this approach is a little limited. We end up manipulating strings around, instead of a proper data representation for HTML.
That applies even more to the template example. Template engines are gluing strings, and shouldn't be used for the same reason the first example shouldn't - there's a language mismatch. HTML is a tree. By treating it as a series of glued strings, it's easy to generate syntactically invalid HTML, which opens you up for XSS problems. It's the exact same problem that led to SQL injections in the past, and why people use parametrized queries now.
The X-Expressions solution, i.e. generating HTML from a tree structure, is the correct one.
I'm not disagreeing in general, but this is reductio ad absurdum. The original formulation of HTML as an SGML vocabulary has very specific formal rules about the kind of escaping needed in a particular context. Not to mention empty/void elements and tag omission/inference. SGML, since its beginning, has entity references which do have types informing about how they can/must be expanded in a given context. The problem is that template "engines" (except SGML proper and very few HTML-aware ones) want to use ad-hoc "${...}" syntax and treat HTML/SGML as an unstructured text string.
element html {
element body {
element div {
attribute id {"main"},
"foo bar!",
1 to 15,
element footer { "this is the footer" }
}
}
}
<html>
<body>
<div id="main">foo bar! 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15<footer>this is the footer</footer>
</div>
</body>
</html>
1: https://www.w3.org/TR/xquery-31/#id-computedConstructors> We've lost a lot of expressiveness: what if we wanted to create a nested list again? We could make it a template macro, but we have to do extra work to call it inside itself, and suddently we have an extra language to learn.
Not that I’ve used jinja aside from some Ansible, I don’t get the second language part for templates or why templates are limiting? Even React’s syntax really isn’t that different from a template as you need to enclose JS expressions in curly braces right?
My favorite HTML server language is Elixir which has a Ruby flavored template engine called using `<%= some_func(@val) %>`. It’s only 4 “tags” [1]. Well maybe since it’s a functional language and everything is an expression the semantics are simpler, mainly limited to whether or not to return a value. Jinja probably is more difficult since Python control flow isn't an expression so you add another layer (probably same in Ruby, Java, etc). Calling functions is as easy as ensuring they’re in the scope of where it’s loaded (or just the current scope when using the ~E sigil. So in the first example encapsulating a sub-lists is just creating a function that returns a template. When compiled it’ll create functions that return IO lists so it’s pretty efficient too [2].
Given that, I prefer having HTML look like HTML.
1: https://hexdocs.pm/eex/EEx.html 2: https://edmz.org/personal/2016/09/06/phoenix_templates-_yup,...
The difference is that they're real, actual JS expressions evaluated in the current scope. Anything you can express in JavaScript you can express in JSX without needing to learn anything new.
There are other solutions, such as representing HTML as structures (often called records in other languages). This is what I prefer - this allows a little static checking. (See `scribble/html` or `urlang/html`).
Others prefer to work with `templates`.
My point got lost: There is more than one "Racket way" of working with html.
[1] https://docs.racket-lang.org/teachpack/2htdpbatch-io.html?q=...
If you're going to do it, do it right... use macros.
Of course, then you'd end up something that looks a lot like Haml (http://haml.info/) with parens everywhere.
https://pugjs.org/language/mixins.html
(I think there's a python port too, but I use the JS version both in Node and in the browser.)
HTML is one case where I think significant whitespace is a really obvious win.
(defn simple-component []
[:div
[:p "I am a component!"]
[:p.someclass
"I have " [:strong "bold"]
[:span {:style {:color "red"}} " and red "] "text."]])You could write a macro to use symbol names literally, so that `div` always means `<div>`, but then you'd have a macro where none was needed, and you'd have no obvious way of using symbols in a non-literal/evaluated way.
Fulcro[1] lies little closer to what you're after. The above would be:
(div
(p "I am a component!")
(p :.someclass
"I have " (strong "bold")
(span {:style {:color "red"}} "and red") "text."))
This is since Fulcro defines the element tags as functions, which are called in order, rather than as a data structure that's interpreted (a la Hiccup).The immediate and obvious benefit of Hiccup over the function hierarchy is that Hiccup can be trivially (and safely) seralized/deserialized.
You don't need a CSS preprocessor if you can manipulate your CSS using the standard data manipulators in your given language, this applies to so many DSL languages like HTML, CSS, SQL, Clojure has Hiccup, Gardern and HoneySQL respectively
Here's a PHP SQL port I've been working on https://github.com/slifin/moonlight (keywords removed for ergonomics but it did lose power when I did that)
If a third party gives you a stringy language to work with you gain power back by creating a data representation, operating on that then converting it back to string at the execution point
In the case of PHP we took the Java route and put everything into objects: https://github.com/zendframework/zend-db, the amount of code required to do that is almost comical when compared, Zend DB also suffers from a lot of bugs/lack of features compared to honeySQL which is about 5 files of data orientated code: https://github.com/jkk/honeysql
I should mention Zend DB is not an outlier here for bulky code have a look at other PHP query builders they're all impenetrable
I use it to generate my academic publications list, although I'm not 100% satisfied with the elegance of the resulting code: https://github.com/anadrome/cl-pubgen/blob/master/generate-p...
> numbers n = docTypeHtml $ do
> H.head $ do
> H.title "Natural numbers"
> body $ do
> p "A list of natural numbers:"
> ul $ forM_ [1 .. n] (li . toHtml)
https://jaspervdj.be/blaze/tutorial.html