Efus.jl is a reactive component framework for Julia. It provides a declarative, indentation-based templating
language that compiles directly to Julia code. Combined with reactivity system from Ionic.jl to build complex,
dynamic user interfaces and other component-based systems in a clean and maintainable way.
Efus.jl is the core templating and reactivity engine of a larger ecosystem. For building graphical user interfaces, it is complemented by Gtak.jl, which provides GTK4 bindings. For application-level services like data storage and task scheduling, it integrates with Atak.jl.
The core philosophy of Efus is to blend the readability of a templating language with the power and performance of native Julia code.
- Declarative Syntax: Write component hierarchies in a clean, minimal, indentation-based syntax.
- Ahead-of-Time Compilation: Efus templates are parsed and converted into pure Julia expressions at macro-expansion time, resulting in zero runtime overhead.
- Powerful Reactivity: Build UIs that automatically update when your data changes using
Reactants (state) andReactors (computed values). - Component-Based Architecture: Encapsulate logic and UI into reusable components with a well-defined lifecycle (
mount!,update!,unmount!).
The @efus_str macro is the entry point to the Efus language. It parses the template and generates corresponding Julia code.
Instantiate a component by its name, followed by properties. This syntax is compiled into a regular Julia constructor call.
- Syntax:
ComponentName property="value" another=variable - Strings: Use double quotes for string literals (e.g.,
text="Hello"). - Variables: Pass Julia variables directly (e.g.,
width=my_width). - Boolean Props: A property without a value is treated as
true(e.g.,disabledis equivalent todisabled=true).
# This Efus code...
efus"""
MyComponent text="Click Me" width=200 active
"""
# ...is compiled into this Julia code:
# MyComponent(text="Click Me", width=200, active=true)Create component hierarchies through indentation. The children are passed as a Vector{Component} to the parent's constructor.
# This Efus code...
efus"""
Window title="My App"
Box orientation=:vertical
Label text="Welcome!"
"""
# ...is compiled into this Julia code:
# Window(title="My App", children=[
# Box(orientation=:vertical, children=[
# Label(text="Welcome!")
# ])
# ])Use standard Julia if/elseif/else and for loops to conditionally or dynamically generate components,
you may add an else clause to for loops which evalueates when the iterable
responds positively to isempty.
If/Else Statements:
efus"""
if is_loading'
Spinner
else
Label text="Content Loaded"
end
"""For Loops:
items = ["One", "Two", "Three"]
efus"""
for item in items
ListItem text=item
else
Nothing
end
"""Snippets are reusable blocks of Efus code, similar to functions or slots. They allow you to pass templating code as an argument to another component, when defined in a component call, but in code blocks or at top level it creates an anonymous function available in local scope. enabling powerful composition patterns like layouts.
- Definition:
snippetName(arg1, arg2::Type=default) ... end - Passing: Pass them to components like any other property.
efus"""
# Define a snippet for the header
header_content(;title)
Label font_weight=:bold text=title
end
# Pass the snippet to a Card component that knows how to render it
Card
content(title) #pass content() as argument
Label text="This is the card content."
header_content title="title is $title"
end
"""You can embed arbitrary Julia code within parentheses () and define anonymous
functions which contains efus expressions with () -> <efus expr>. This is useful for
defining local variables, running logic, or calling functions directly within your template.
efus"""
(
items = ["One", "Two", "Three"];
current_user = "Admin"
)
Label text="Welcome, $(current_user)!"
for item in items
Label text=item
"""This is the supertype for all components. To create a new component, you define a struct that subtypes Component and implement its lifecycle methods.
mount!(component, parent): Called to initialize the component. This is where you create backend objects (e.g., widgets, renderers), set up reactive subscriptions, and attach the component to its parent.update!(component): Called when a component's properties have been marked as "dirty" (changed). This is where you apply updates to the backend objects.unmount!(component): Called to clean up all resources (e.g., destroy widgets, calldenature!on catalysts) to prevent memory leaks.
This example shows how the concepts fit together, without tying it to a specific UI backend.
using Efus, Ionic
# 1. Define the Component Struct
struct Counter <: Component
# --- State ---
count::Reactant{Int}
# --- Internal Fields ---
on_click::Function
catalyst::Catalyst
backend_ref # A reference to the backend object
end
# 2. Define the Constructor
function Counter(; initial_value=0, on_click=()->nothing)
Counter(Reactant(initial_value), on_click, Catalyst(), nothing)
end
# 3. Implement Lifecycle Methods
function Efus.mount!(c::Counter, parent)
# Create the backend representation (e.g., a button)
c.backend_ref = create_backend_button("Count: $(c.count[])")
# Set up the click handler
set_backend_onclick(c.backend_ref, () -> c.on_click(c))
# Set up reactivity: when `count` changes, update the button text
catalyze!(c.catalyst, c.count) do count
set_backend_button_text(c.backend_ref, "Count: $(count[])")
end
# Attach to the parent in the backend
add_to_backend_parent(parent, c.backend_ref)
end
function Efus.unmount!(c::Counter)
# Clean up subscriptions to prevent memory leaks
denature!(c.catalyst)
# Destroy the backend object
destroy_backend_object(c.backend_ref)
end
# --- Usage ---
increment(c::Counter) = c.count[] += 1
counter = nothing
content = efus"""
Counter initial_value=5 onclick=() -> (increment(content[1]))
"""Efus's reactivity is powered by Ionic.jl. The ' syntax is automatically enabled inside @efus_str templates, making it easy to work with reactive state.
efus"""
(
first_name = Reactant("John");
last_name = Reactant("Doe");
full_name = @reactor "$(first_name') $(last_name')";
)
Label text="Full Name: $(full_name')"
Entry placeholder="First Name" onchange=(new_text -> first_name' = new_text)
"""For a full overview of the reactivity system, see the Ionic.jl README.
For conventions on indentation, naming, and formatting, please see the STYLE_GUIDE.md.
A key feature of Efus is its performance. This is achieved because efus"..." is not an interpreted language; it's a macro that compiles your template into native Julia code before your program even runs. This means you get the readability of a templating language with zero runtime overhead.
The process is, in short:
- Tokenizing: The raw text is broken down into fundamental tokens (like identifiers, keywords, and indentation).
- Parsing: The tokens are used to build an Abstract Syntax Tree (AST), which is a hierarchical representation of your component structure.
- Code Generation: The AST is traversed and converted into a standard Julia
Expr(expression). During this stage, reactive'syntax is also translated intogetvalueandsetvalue!calls.
The final Julia expression is what gets compiled, making your Efus templates just as fast as handwritten Julia code.
- Creating Components: A detailed guide for building your own Efus components and component libraries.
For conventions on indentation, naming, and formatting, please see the STYLE_GUIDE.md.