Skip to content

Commit

Permalink
Add @__once__ macro
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelHatherly committed Dec 8, 2024
1 parent 42d8b70 commit c5c2bd8
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 2 deletions.
38 changes: 38 additions & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,44 @@ end
end
```

## `@__once__`

When you need to render HTML to a page only once per page, for example a JS
dependency that only needs including once via `<script>`, you can use this
macro to do that. It ensures that during a single `@render` call the contents
of each `@__once__` are only evaluated once even if the rendered components are
called more that once.

Most common use cases are for including `@link`, `@style`, or `@script` tags.

```julia
@component function jquery()
@__once__ begin
@script {src = "https://code.jquery.com/jquery-3.6.0.min.js"}
end
end
@deftag macro jquery end

@component function jq_button()
@jquery
@button "Click Me"
end
@deftag macro jq_button end

@component function page()
@html begin
@head begin
@jquery
end
@body begin
@h1 "Hello, World!"
@jq_button
end
end
end
@deftag macro page end
```

## Property Names

Typically property names, which are defined between `{}`s are written as Julia
Expand Down
2 changes: 2 additions & 0 deletions src/HypertextTemplates.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import SimpleBufferStream
# Exports:

export @<
export @__once__
export @__slot__
export @cm_component
export @component
Expand Down Expand Up @@ -40,6 +41,7 @@ include("template-source-lookup.jl")
include("render.jl")
include("stream.jl")
include("cmfile.jl")
include("once.jl")

# Initialization:

Expand Down
26 changes: 26 additions & 0 deletions src/once.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""
@__once__ expr
Evaluate `expr` only once per `@render` call.
"""
macro __once__(expr)
key = QuoteNode(Symbol(string(__module__, ':', __source__)))
io = esc(S"io")
return quote
if $(@__MODULE__)._missing_once_key($io, $key)
$(esc(expr))
$(@__MODULE__)._add_once_key!($io, $key)
end
nothing
end
end

function _get_once(io::IO)
once_ref = get(io, :__once__, nothing)
isassigned(once_ref) || (once_ref[] = Set{Symbol}())
once = once_ref[]
return isnothing(once) ? error("missing `once` object.") : once
end

_missing_once_key(io::IO, key::Symbol) = !(key in _get_once(io))
_add_once_key!(io::IO, key::Symbol) = push!(_get_once(io), key)
7 changes: 5 additions & 2 deletions src/render-macro.jl
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,19 @@ end

function _render(dst, dom_thunk::Function, source::Tuple{String,Integer})
io = _render_dst(dst)
ctx = IOContext(io, :__root__ => source)
ctx = IOContext(io, :__root__ => source, _once_ref())
dom_thunk(ctx, nothing)
return _render_return(io, dst)
end
function _render(dst, dom_thunk::Function, source::Nothing)
io = _render_dst(dst)
dom_thunk(io, nothing)
ctx = IOContext(io, _once_ref())
dom_thunk(ctx, nothing)
return _render_return(io, dst)
end

_once_ref() = :__once__ => Ref{Set{Symbol}}()

_render_dst(io::IO) = io
_render_dst(::Type{String}) = IOBuffer()
_render_dst(::Type{Vector{UInt8}}) = IOBuffer()
Expand Down
1 change: 1 addition & 0 deletions test/references/basics/once-button-1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script><button>Click Me</button>
1 change: 1 addition & 0 deletions test/references/basics/once-button-2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script><button>Click Me</button><button>Click Me</button>
1 change: 1 addition & 0 deletions test/references/basics/once-page.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!DOCTYPE html><html><head><script src="https://code.jquery.com/jquery-3.6.0.min.js"></script></head><body><h1>Hello, World!</h1><button>Click Me</button></body></html>
38 changes: 38 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,32 @@ end
end
@deftag macro streaming end

@component function once_jquery()
@__once__ begin
@script {src = "https://code.jquery.com/jquery-3.6.0.min.js"}
end
end
@deftag macro once_jquery end

@component function once_button()
@once_jquery
@button "Click Me"
end
@deftag macro once_button end

@component function once_page()
@html begin
@head begin
@once_jquery
end
@body begin
@h1 "Hello, World!"
@once_button
end
end
end
@deftag macro once_page end

@testset "HypertestTemplates" begin
@testset "Basics" begin
render_test("references/basics/html-elements.txt") do io
Expand Down Expand Up @@ -166,6 +192,18 @@ end
@span {"x-show" := "open"} "Content..."
end
end
render_test("references/basics/once-button-1.txt") do io
@render io @once_button
end
render_test("references/basics/once-button-2.txt") do io
@render io begin
@once_button
@once_button
end
end
render_test("references/basics/once-page.txt") do io
@render io @once_page
end
end
@testset "Markdown" begin
render_test("references/markdown/markdown.txt") do io
Expand Down

0 comments on commit c5c2bd8

Please sign in to comment.