Core Concepts
Understanding the core concepts of HypertextTemplates.jl will help you use the library effectively and write maintainable templates.
Macro-Based DSL Philosophy
HypertextTemplates.jl uses Julia's macro system to create a domain-specific language (DSL) for HTML generation. This approach provides several benefits:
Compile-Time Optimization
using HypertextTemplates
using HypertextTemplates.Elements
# This macro call...
html = @render @div {class = "container"} @p "Hello"
<div class="container"><p>Hello</p></div>
Native Julia Integration
The DSL feels like natural Julia code because it is Julia code:
using HypertextTemplates
using HypertextTemplates.Elements
# Regular Julia control flow works seamlessly
@render @ul begin
for i in 1:5
if isodd(i)
@li {class = "odd"} "Item " $i
else
@li {class = "even"} "Item " $i
end
end
end
<ul>
<li class="odd">Item 1</li>
<li class="even">Item 2</li>
<li class="odd">Item 3</li>
<li class="even">Item 4</li>
<li class="odd">Item 5</li>
</ul>
Type Safety
Julia's type system helps catch errors at compile time:
using HypertextTemplates
using HypertextTemplates.Elements
@component function typed_list(; items::Vector{String})
@ul begin
for item in items
@li $item
end
end
end
@deftag macro typed_list end
# Julia's type system helps catch errors
items = ["Apple", "Banana", "Cherry"]
html = @render @typed_list {items}
<ul>
<li>Apple</li>
<li>Banana</li>
<li>Cherry</li>
</ul>
The {}
Attribute Syntax
Building on the macro foundation, attributes use a special {}
syntax that resembles Julia's NamedTuple syntax:
Basic Attributes
using HypertextTemplates
using HypertextTemplates.Elements
# Simple attributes
@render @div {id = "main", class = "container"} "Content"
<div id="main" class="container">Content</div>
using HypertextTemplates
using HypertextTemplates.Elements
# Computed attributes
width = 100
@render @img {src = "/logo.png", width = width * 2}
<img src="/logo.png" width="200">
Attribute Name Shortcuts
When variable names match attribute names:
using HypertextTemplates
using HypertextTemplates.Elements
class = "active"
disabled = true
# Instead of {class = class, disabled = disabled}
@render @button {class, disabled} "Click me"
<button class="active" disabled>Click me</button>
Attribute Spreading
Spread multiple attributes from a collection:
using HypertextTemplates
using HypertextTemplates.Elements
common_attrs = (class = "btn", type = "button")
@render @button {id = "submit", common_attrs...} "Submit"
<button id="submit" class="btn" type="button">Submit</button>
Boolean Attributes
Boolean handling follows HTML5 semantics:
using HypertextTemplates
using HypertextTemplates.Elements
# true renders the attribute name only
@render @input {type = "checkbox", checked = true}
<input type="checkbox" checked>
# false omits the attribute entirely
@render @input {type = "checkbox", checked = false}
<input type="checkbox">
Text Rendering and Interpolation
Variable Interpolation with $
The $
syntax marks expressions for rendering with automatic escaping:
using HypertextTemplates
using HypertextTemplates.Elements
user_input = "<script>alert('xss')</script>"
html = @render @p "User said: " $user_input
<p>User said: <script>alert('xss')</script></p>
The @text
Macro
The $
syntax is actually shorthand for @text
:
using HypertextTemplates
using HypertextTemplates.Elements
value = 42
# These are equivalent
html1 = @render @p "\$ Value: " $value
<p>$ Value: 42</p>
html2 = @render @p "@text Value: " @text value
<p>@text Value: 42</p>
a, b = 10, 20
# @text can handle complex expressions
html3 = @render @p @text "The sum is $(a + b)"
<p>The sum is 30</p>
Mixed Content
You can mix different content types:
using HypertextTemplates
using HypertextTemplates.Elements
dynamic_var = "dynamic content"
html = @render @div begin
@span "Static text " # String literal
@code $dynamic_var # Escaped variable
@p "bold" # Nested element
@strong " more text" # Another literal
end
<div>
<span>Static text </span>
<code>dynamic content</code>
<p>bold</p>
<strong> more text</strong>
</div>
Zero-Allocation Design
The performance benefits of the macro system extend to the rendering pipeline through zero-allocation design:
Direct IO Streaming
Instead of building a DOM tree, content streams directly to IO:
using HypertextTemplates
using HypertextTemplates.Elements
# No intermediate string allocations
io = IOBuffer()
@render io @div begin
for i in 1:5
@p "Paragraph " $i
end
end
result = String(take!(io))
<div>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
<p>Paragraph 3</p>
<p>Paragraph 4</p>
<p>Paragraph 5</p>
</div>
Efficient String Building
The rendering process uses Julia's efficient IO system:
using HypertextTemplates
using HypertextTemplates.Elements
# Internally uses write() calls, not string concatenation
html = @render @div begin
@h1 "Title"
@p "Content"
end
# This is equivalent to direct write() calls:
# write(io, "<div>")
# write(io, "<h1>")
# write(io, "Title")
# write(io, "</h1>")
# ...
<div>
<h1>Title</h1>
<p>Content</p>
</div>
Rendering Pipeline
The rendering pipeline works as follows:
- Macro Expansion: Templates are transformed into Julia code at compile time
- IO Target: All output goes to an IO stream (provided or created)
- Direct Writing: HTML strings and escaped content are written directly
- No Buffering: Content flows straight through without intermediate storage
This design means:
- Memory usage is constant regardless of output size
- First byte is written immediately (no buffering)
- Suitable for very large documents
- Optimal for streaming responses
Control Flow Integration
Since templates are Julia code, all control flow constructs work naturally:
Loops
using HypertextTemplates
using HypertextTemplates.Elements
# for loops
collection = ["Apple", "Banana", "Cherry"]
html1 = @render @ul for item in collection
@li $item
end
<ul>
<li>Apple</li>
<li>Banana</li>
<li>Cherry</li>
</ul>
# while loops
count = 0
html2 = @render @div begin
while count < 3
@p "Count: " $count
global count += 1
end
end
<div>
<p>Count: 0</p>
<p>Count: 1</p>
<p>Count: 2</p>
</div>
# comprehensions
html3 = @render @select begin
[@option {value = i} "Option " $i for i in 1:5]
end
println("\nComprehension:")
<select>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
<option value="4">Option 4</option>
<option value="5">Option 5</option>
</select>
Conditionals
All conditional forms are supported:
using HypertextTemplates
using HypertextTemplates.Elements
# if-else
condition = true
html1 = @render @div begin
if condition
@p "True branch"
else
@p "False branch"
end
end
<div><p>True branch</p></div>
# ternary operator
isactive = false
html2 = @render @p {class = isactive ? "active" : "inactive"} "Status"
<p class="inactive">Status</p>
# short-circuit evaluation
hasdata = false
html3 = @render @div begin
hasdata && @p "Data is available"
!hasdata && @p "No data available"
end
<div><p>No data available</p></div>
Pattern Matching
Works with any macro-based control flow:
# With Match.jl (example)
@div begin
@match value begin
1 => @p "One"
2 => @p "Two"
_ => @p "Other"
end
end
Component Architecture
The macro system and control flow integration come together in HypertextTemplates' component architecture:
Function-Based Components
using HypertextTemplates
using HypertextTemplates.Elements
@component function alert(; type = "info", message)
classes = "alert alert-" * type
@div {class = classes, role = "alert"} $message
end
@deftag macro alert end
# Use the component
html = @render @alert {type = "warning", message = "This is a warning!"}
<div class="alert alert-warning" role="alert">This is a warning!</div>
Composition
Components compose naturally:
using HypertextTemplates
using HypertextTemplates.Elements
# Reuse the alert component from above
@component function alert(; type = "info", message)
classes = "alert alert-" * type
@div {class = classes, role = "alert"} $message
end
@deftag macro alert end
@component function alert_list(; alerts)
@div {class = "alert-container"} begin
for alert in alerts
@alert {type = alert.type, message = alert.message}
end
end
end
@deftag macro alert_list end
# Use the composed component
alerts = [
(type = "info", message = "Information message"),
(type = "warning", message = "Warning message"),
(type = "error", message = "Error message")
]
html = @render @alert_list {alerts}
<div class="alert-container">
<div class="alert alert-info" role="alert">Information message</div>
<div class="alert alert-warning" role="alert">Warning message</div>
<div class="alert alert-error" role="alert">Error message</div>
</div>
Component Transformation
The @component
macro transforms the function to:
- Accept rendering context (IO stream)
- Handle slot content (both default and named slots)
- Manage source location tracking (in development mode)
- Support streaming render (for async operations)
For example, this component:
@component function example(; prop)
@div $prop
end
Is transformed into a function that:
- Accepts an internal
__io__
parameter for rendering - Can receive slot content via hidden parameters
- Tracks its definition location for debugging
- Works seamlessly with
StreamingRender
Performance Considerations
The zero-allocation design and macro system combine to optimize performance at multiple levels:
Compile-Time Work
using HypertextTemplates
using HypertextTemplates.Elements
# This template structure is analyzed at compile time
@component function static_heavy()
@div {class = "wrapper"} begin
@header begin
@nav begin
@ul begin
@li @a {href = "/"} "Home"
@li @a {href = "/about"} "About"
end
end
end
end
end
@deftag macro static_heavy end
# The structure is compiled, not interpreted at runtime
html = @render @static_heavy
<div class="wrapper">
<header>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
</header>
</div>
Runtime Efficiency
Only dynamic parts are computed at runtime:
using HypertextTemplates
using HypertextTemplates.Elements
@component function dynamic_list(; items)
# Static structure compiled, only loop runs at runtime
@ul {class = "list"} begin
for item in items # Only this loop runs at runtime
@li $item
end
end
end
@deftag macro dynamic_list end
# Only the loop execution is runtime work
items = ["Dynamic 1", "Dynamic 2", "Dynamic 3"]
html = @render @dynamic_list {items}
<ul class="list">
<li>Dynamic 1</li>
<li>Dynamic 2</li>
<li>Dynamic 3</li>
</ul>
HTML Escaping Strategy
Complementing the performance features, HypertextTemplates provides automatic security through its escaping strategy:
Automatic Escaping
All dynamic content is escaped by default:
using HypertextTemplates
using HypertextTemplates.Elements
unsafe = "<script>alert('xss')</script>"
html = @render @p $unsafe
<p><script>alert('xss')</script></p>
Escape Rules
- String literals in templates are NOT escaped (trusted content)
- Variables and expressions with
$
or@text
ARE escaped - Attribute values from variables ARE escaped
- Element content from components is already rendered (not double-escaped)
Performance
Escaping uses optimized routines:
- Fast-path for strings without special characters
- Efficient replacement for strings with special characters
- Minimal allocations during escaping
Summary
These core concepts build on each other:
- Macros provide the foundation with compile-time optimization
{}
attributes extend the macro syntax for properties- Text rendering handles content with automatic security
- Zero-allocation design ensures performance at scale
- Control flow integration leverages Julia's expressiveness
- Components combine all concepts for reusable templates
Each concept reinforces the others, creating a cohesive system that's fast, safe, and natural to use.