Lavash.SparkHeex (Lavash v0.4.0-rc.3)

Copy Markdown View Source

Spike: a Spark DSL extension that treats HEEx templates as a first-class DSL element, not as opaque sigil contents inside a function body.

See Lavash.SparkHeex.Dsl and Lavash.SparkHeex.TemplateMacro for the moving parts. The spike write-up lives at the top of this file:

Why

Lavash's existing approach is:

render fn assigns ->
  ~L"""<div>{@count}</div>"""
end

The DSL doesn't know what's inside the sigil until a separate transformer walks the AST and extracts the string. That means DSL-level analysis (e.g. "is @count a declared state field?") can't happen at DSL build time.

This spike introduces a template do ... end section. The body holds a single ~H"..." sigil; the macro extracts the source string at parse time and persists it onto the Spark DSL state, where Spark transformers can inspect it alongside the declared state entities.

Approach trade-offs

Elixir's parser cannot natively accept <button>...</button> inside a do block (it parses < and > as operators). The four options were:

  1. Sigil (~H"...") — works with stock Elixir parser, no fork needed.
  2. Custom block delimiter via tokenizer hook — would need a Spark/Elixir parser fork.
  3. Heredoc string argument to a macro — works but feels less native.
  4. Fork Spark to add an HTML tokenizer hook — biggest scope, out of spike budget.

This spike picks (1). The payoff being demonstrated is not the surface syntax — it's that the template is a first-class Spark entity that transformers can validate against the rest of the DSL state at DSL build time.

Example

defmodule MyComponent do
  use Lavash.SparkHeex

  state :count, :integer, default: 0

  template do
    ~H"""
    <button>Count: {@count}</button>
    """
  end
end

MyComponent.render(%{count: 5})
# => %Phoenix.LiveView.Rendered{...}  rendering "Count: 5"

Validation happens at compile time: if the template references @bogus, Spark raises a DslError from inside its transformer pipeline.

Options

  • :extensions (list of module that adopts Spark.Dsl.Extension) - A list of DSL extensions to add to the Spark.Dsl

  • :otp_app (atom/0) - The otp_app to use for any application configurable options

  • :fragments (list of module/0) - Fragments to include in the Spark.Dsl. See the fragments guide for more.