Repository: mhanberg/temple
Pull request #182 (2022-09-28 04:12:41)

Align component model with HEEx/Surface
This change aligns the component model with HEEx/Surface. This shoudl
allow one to interop components created in any syntax with any other
syntax.

The advantage of this is folks can utilize component packages created
using a different syntax.

This should also close https://github.com/mhanberg/temple/issues/130.

## TODO

- [x] Test interop of components between HEEx and Temple.
- [x] Test passing single args to slots that are not maps.
- [x] Slot attributes
- [x] Improve code quality. Currently was a hacky pass to get it to work at all.
- [x] Docs
- [x] Changelog
- [x] Migration Guide

## Notes

Example project showing how component from both template packages can be used with each other seamlessly https://github.com/mhanberg/temple_heex_interop/blob/main/lib/temple_heex_interop_web/views/page_view.ex.

<img width="955" alt="image" src="https://user-images.githubusercontent.com/5523984/192696574-fc390add-66e6-4ba9-9195-45a4842ea5c1.png">



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ef17192..85827d2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,19 @@
 
 ## Main
 
+### Enhancements
+
+- Temple components are now compatible with HEEx/Surface components! Some small tweaks to the component implementation has made this possible. Please see the guides for more information.
+- Multiple instances of the same slot name can now be declared and then rendered inside the component (similar to HEEx and Surface).
+- You can now pass arbitrary data to slots, and it does not need to be a map or a keyword list. I don't think this is a breaking change, but please submit an issue if you notice it is.
+- Slot attributes. You can now pass data into a slot from the definition site and use it at the call site (inside the component).
+
+### Breaking Changes
+
+- Rendering slots is now done by passing the assign with the slot name to the `slot` keyword instead of name as an atom. If this slot has multiple definitions, you can loop through them and render each one individually, or render them all at once. Please see the migration guide for more information.
+- The `:default` slot has been renamed to `:inner_block`. This is to be easily compatible with HEEx/Surface. Please see the migration guide for more information.
+- Capturing the data being passed into a slot is now defined using the `:let` attribute. Please see the migration guide for more information.
+
 ### 0.10.0
 
 ### Enhancements
diff --git a/README.md b/README.md
index 40b7db1..ce978a8 100644
--- a/README.md
+++ b/README.md
@@ -67,6 +67,8 @@ Temple components are simple to write and easy to use.
 
 Unlike normal partials, Temple components have the concept of "slots", which are similar [Vue](https://v3.vuejs.org/guide/component-slots.html#named-slots). You can also refer to HEEx and Surface for examples of templates that have the "slot" concept.
 
+Temple components are compatible with HEEx and Surface components and can be shared.
+
 Please see the [guides](https://hexdocs.pm/temple/components.html) for more details.
 
 ```elixir
@@ -77,15 +79,15 @@ defmodule MyAppWeb.Component do
     temple do
       section do
         div do
-          slot :header
+          slot @header
         end
 
         div do
-          slot :default
+          slot @inner_block
         end
 
         div do
-          slot :footer
+          slot @footer
         end
       end
     end
diff --git a/guides/components.md b/guides/components.md
index 921983f..9e90b0c 100644
--- a/guides/components.md
+++ b/guides/components.md
@@ -54,7 +54,7 @@ Slots are defined and rendered using the `slot` keyword. This is similar to the
 
 ### Default Slot
 
-The default slot can be rendered from within your component by passing the `slot` the atom `:default`. Let's redefine our button component using slots.
+The default slot can be rendered from within your component by passing the `slot` the `@inner_block` assign. Let's redefine our button component using slots.
 
 ```elixir
 defmodule MyApp.Components do
@@ -63,7 +63,7 @@ defmodule MyApp.Components do
   def button(assigns) do
     temple do
       button type: "button", class: "bg-blue-800 text-white rounded #{@class}" do
-        slot :default
+        slot @inner_block
       end
     end
   end
@@ -109,18 +109,18 @@ defmodule MyApp.Components do
       div class: "card" do
         header class: "card-header", style: "background-color: @f5f5f5" do
           p class: "card-header-title" do
-            slot :header
+            slot @header
           end
         end
 
         div class: "card-content" do
           div class: "content" do
-            slot :default
+            slot @inner_block
           end
         end
 
         footer class: "card-footer", style: "background-color: #f5f5f5" do
-          slot :footer
+          slot @footer
         end
       end
     end
@@ -145,8 +145,8 @@ def MyApp.CardExample do
         "This example demonstrates how to create components with multiple, named slots"
 
         slot :footer do
-          a href="#", class: "card-footer-item", do: "Footer Item 1"
-          a href="#", class: "card-footer-item", do: "Footer Item 2"
+          a href: "#", class: "card-footer-item", do: "Footer Item 1"
+          a href: "#", class: "card-footer-item", do: "Footer Item 2"
         end
       end
     end
@@ -154,11 +154,15 @@ def MyApp.CardExample do
 end
 ```
 
-## Passing Data Through Slots
+## Passing data to and through Slots
 
-Sometimes it is necessary to pass data from a component definition back to the call site.
+Sometimes it is necessary to pass data _into_ a slot (hereby known as *slot attributes*) from the call site and _from_ a component definition (hereby known as *slot arguments*) back to the call site.
 
-Let's look at what a `table` component could look like.
+Let's look at what a `table` component could look like. Here we observe we access an attribute in the slot in the header with `col.label`.
+
+This example is taken from the HEEx documentation to demonstrate how you can build the same thing with Temple.
+
+Note: Slot attributes can only be accessed on an individual slot, so if you define a single slot definition, you still need to loop through it to access it, as they are stored as a list.
 
 #### Definition
 
@@ -166,30 +170,23 @@ Let's look at what a `table` component could look like.
 defmodule MyApp.Components do
   import Temple
 
-  def cols(items) do
-    items
-    |> List.first()
-    |> Map.keys()
-    |> Enum.sort()
-  end
-
   def table(assigns) do
     temple do
       table do
         thead do
           tr do
-            for col <- cols(@entries) do
-              tr do: String.upcase(to_string(col))
+            for col <- @col do
+              th do: col.label # 👈 accessing a slot attribute
             end
           end
         end
 
         tbody do
-          for row <- @entries do
+          for row <- @rows do
             tr do
-              for col <- cols(@entries) do
+              for col <- @col do
                 td do
-                  slot :cell, %{value: row[cell]}
+                  slot col, row
                 end
               end
             end
@@ -203,7 +200,7 @@ end
 
 #### Usage
 
-When we render the slot, we can pattern match on the data passed through the slot. If this seems familiar, it's because this is the same syntax you use when writing your tests using `ExUnit.Case.test/3`.
+When we render the slot, we can pattern match on the data passed through the slot via the `:let` attribute.
 
 ```elixir
 def MyApp.TableExample do
@@ -213,24 +210,16 @@ def MyApp.TableExample do
   def render(assigns) do
     temple do
       section do
-        h2 do: "Inventory Levels"
-
-        c &table/1, entries: @item_inventories do
-          slot :cell, %{value: value} do
-            case value do
-              0 ->
-                span class: "font-bold" do
-                  "Out of stock!"
-                end
+        h2 do: "Users"
 
-              level when is_number(level) ->
-                span do
-                  "#{level} in stock"
-                end
+        c &table/1, rows: @users do
+          #          👇 defining the parameter for the slot argument
+          slot :col, let: user, label: "Name" do # 👈 passing a slot attribute
+            user.name
+          end
 
-              _ ->
-                span do: value
-            end
+          slot :col, let: user, label: "Address" do
+            user.address
           end
         end
       end
diff --git a/guides/migrating/0.10-to-0.11.md b/guides/migrating/0.10-to-0.11.md
new file mode 100644
index 0000000..9f84ceb
--- /dev/null
+++ b/guides/migrating/0.10-to-0.11.md
@@ -0,0 +1,95 @@
+# Migrating from 0.10 to 0.11
+
+Most of the changes in this release are related to tweaking Temple's component model to align with HEEx & Surface.
+
+## Rendering Slots
+
+Slots are now available as assigns in the component and are rendered as such.
+
+### Before
+
+```elixir
+def my_component(assign) do
+  temple do
+    span do
+      slot :a_slot
+    end
+  end
+end
+```
+
+### After
+
+```elixir
+def my_component(assign) do
+  temple do
+    span do
+      slot @a_slot
+    end
+  end
+end
+```
+
+## :default slot has been renamed to :inner_block
+
+The main body of a component has been renamed from `:default` to `:inner_block`.
+
+Note: The "after" example also includes the necessary change specified above.
+
+### Before
+
+```elixir
+def my_component(assign) do
+  temple do
+    span do
+      slot :default
+    end
+  end
+end
+```
+
+### After
+
+```elixir
+def my_component(assign) do
+  temple do
+    span do
+      slot @inner_block
+    end
+  end
+end
+```
+
+## Passing data into slots
+
+The syntax for capturing data being passed from the call site of a slot to the definition of a slot (or put another way, from the definition of a component to the call site of the component) has changed. 
+
+You now capture it as the value of the `:let` attribute on the slot definition.
+
+### Before
+
+```elixir
+def my_component(assign) do
+  temple do
+    c &my_component/1 do
+      slot :a_slot, %{some: value} do
+        "I'm using some #{value}"
+      end
+    end
+  end
+end
+```
+
+### After
+
+```elixir
+def my_component(assign) do
+  temple do
+    c &my_component/1 do
+      slot :a_slot, let: %{some: value} do
+        "I'm using some #{value}"
+      end
+    end
+  end
+end
+```
diff --git a/lib/temple.ex b/lib/temple.ex
index 0b8cd1f..cae4248 100644
--- a/lib/temple.ex
+++ b/lib/temple.ex
@@ -93,6 +93,8 @@ defmodule Temple do
   <link href="/css/site.css">
   ```
   """
+  @doc false
+  def engine(), do: @engine
 
   defmacro temple(block) do
     opts = [engine: engine()]
@@ -104,10 +106,69 @@ defmodule Temple do
   end
 
   @doc false
-  def component(func, assigns) do
+  def component(func, assigns, _) do
     apply(func, [assigns])
   end
 
+  defmacro inner_block(_name, do: do_block) do
+    __inner_block__(do_block)
+  end
+
   @doc false
-  def engine(), do: @engine
+  def __inner_block__([{:->, meta, _} | _] = do_block) do
+    inner_fun = {:fn, meta, do_block}
+
+    quote do
+      fn arg ->
+        _ = var!(assigns)
+        unquote(inner_fun).(arg)
+      end
+    end
+  end
+
+  def __inner_block__(do_block) do
+    quote do
+      fn arg ->
+        _ = var!(assigns)
+        unquote(do_block)
+      end
+    end
+  end
+
+  defmacro render_slot(slot, arg) do
+    quote do
+      unquote(__MODULE__).__render_slot__(unquote(slot), unquote(arg))
+    end
+  end
+
+  @doc false
+  def __render_slot__([], _), do: nil
+
+  def __render_slot__([entry], argument) do
+    call_inner_block!(entry, argument)
+  end
+
+  def __render_slot__(entries, argument) when is_list(entries) do
+    assigns = %{}
+    _ = assigns
+
+    temple do
+      for entry <- entries do
+        call_inner_block!(entry, argument)
+      end
+    end
+  end
+
+  def __render_slot__(entry, argument) when is_map(entry) do
+    entry.inner_block.(argument)
+  end
+
+  defp call_inner_block!(entry, argument) do
+    if !entry.inner_block do
+      message = "attempted to render slot #{entry.__slot__} but the slot has no inner content"
+      raise RuntimeError, message
+    end
+
+    entry.inner_block.(argument)
+  end
 end
diff --git a/lib/temple/ast.ex b/lib/temple/ast.ex
index 8c27a78..9c4fe29 100644
--- a/lib/temple/ast.ex
+++ b/lib/temple/ast.ex
@@ -2,17 +2,17 @@ defmodule Temple.Ast do
   @moduledoc false
 
   @type t ::
-          Temple.Parser.Empty.t()
-          | Temple.Parser.Text.t()
-          | Temple.Parser.Components.t()
-          | Temple.Parser.Slot.t()
-          | Temple.Parser.NonvoidElementsAliases.t()
-          | Temple.Parser.VoidElementsAliases.t()
-          | Temple.Parser.AnonymousFunctions.t()
-          | Temple.Parser.RightArrow.t()
-          | Temple.Parser.DoExpressions.t()
-          | Temple.Parser.Match.t()
-          | Temple.Parser.Default.t()
+          Temple.Ast.Empty.t()
+          | Temple.Ast.Text.t()
+          | Temple.Ast.Components.t()
+          | Temple.Ast.Slot.t()
+          | Temple.Ast.NonvoidElementsAliases.t()
+          | Temple.Ast.VoidElementsAliases.t()
+          | Temple.Ast.AnonymousFunctions.t()
+          | Temple.Ast.RightArrow.t()
+          | Temple.Ast.DoExpressions.t()
+          | Temple.Ast.Match.t()
+          | Temple.Ast.Default.t()
 
   def new(module, opts \\ []) do
     struct(module, opts)
diff --git a/lib/temple/parser/anonymous_functions.ex b/lib/temple/ast/anonymous_functions.ex
similarity index 70%
rename from lib/temple/parser/anonymous_functions.ex
rename to lib/temple/ast/anonymous_functions.ex
index 03f059c..e973e22 100644
--- a/lib/temple/parser/anonymous_functions.ex
+++ b/lib/temple/ast/anonymous_functions.ex
@@ -1,4 +1,4 @@
-defmodule Temple.Parser.AnonymousFunctions do
+defmodule Temple.Ast.AnonymousFunctions do
   @moduledoc false
   @behaviour Temple.Parser
 
@@ -11,7 +11,7 @@ defmodule Temple.Parser.AnonymousFunctions do
 
   @impl true
   def applicable?({_, _, args}) do
-    import Temple.Parser.Utils, only: [split_args: 1]
+    import Temple.Ast.Utils, only: [split_args: 1]
 
     args
     |> split_args()
@@ -23,9 +23,9 @@ defmodule Temple.Parser.AnonymousFunctions do
 
   @impl true
   def run({_name, _, args} = expression) do
-    {_do_and_else, args} = Temple.Parser.Utils.split_args(args)
+    {_do_and_else, args} = Temple.Ast.Utils.split_args(args)
 
-    {_args, func_arg, _args2} = Temple.Parser.Utils.split_on_fn(args, {[], nil, []})
+    {_args, func_arg, _args2} = Temple.Ast.Utils.split_on_fn(args, {[], nil, []})
 
     {_func, _, [{_arrow, _, [[{_arg, _, _}], block]}]} = func_arg
 
diff --git a/lib/temple/parser/components.ex b/lib/temple/ast/components.ex
similarity index 55%
rename from lib/temple/parser/components.ex
rename to lib/temple/ast/components.ex
index 8bf8203..8ed4341 100644
--- a/lib/temple/parser/components.ex
+++ b/lib/temple/ast/components.ex
@@ -1,4 +1,4 @@
-defmodule Temple.Parser.Components do
+defmodule Temple.Ast.Components do
   @moduledoc false
   @behaviour Temple.Parser
 
@@ -6,8 +6,7 @@ defmodule Temple.Parser.Components do
 
   typedstruct do
     field :function, function()
-    field :assigns, map()
-    field :children, [map()]
+    field :arguments, map()
     field :slots, [function()]
   end
 
@@ -22,27 +21,28 @@ defmodule Temple.Parser.Components do
   def run({:c, _meta, [component_function | args]}) do
     {do_and_else, args} =
       args
-      |> Temple.Parser.Utils.split_args()
+      |> Temple.Ast.Utils.split_args()
 
-    {do_and_else, assigns} = Temple.Parser.Utils.consolidate_blocks(do_and_else, args)
+    {do_and_else, arguments} = Temple.Ast.Utils.consolidate_blocks(do_and_else, args)
 
     {default_slot, {_, named_slots}} =
       if children = do_and_else[:do] do
         Macro.prewalk(
           children,
-          {component_function, %{}},
+          {component_function, []},
           fn
             {:c, _, [name | _]} = node, {_, named_slots} ->
               {node, {name, named_slots}}
 
             {:slot, _, [name | args]} = node, {^component_function, named_slots} ->
-              {assigns, slot} = split_assigns_and_children(args, Macro.escape(%{}))
+              {arguments, slot} = split_assigns_and_children(args, nil)
 
               if is_nil(slot) do
                 {node, {component_function, named_slots}}
               else
-                {nil,
-                 {component_function, Map.put(named_slots, name, %{assigns: assigns, slot: slot})}}
+                {parameter, attributes} = Keyword.pop(arguments || [], :let)
+                new_slot = {name, %{parameter: parameter, slot: slot, attributes: attributes}}
+                {nil, {component_function, named_slots ++ [new_slot]}}
               end
 
             node, acc ->
@@ -57,37 +57,45 @@ defmodule Temple.Parser.Components do
       if default_slot == nil do
         []
       else
-        Temple.Parser.parse(default_slot)
+        [
+          Temple.Ast.new(
+            Temple.Ast.Slottable,
+            name: :inner_block,
+            content: Temple.Parser.parse(default_slot)
+          )
+        ]
       end
 
     slots =
-      for {name, %{slot: slot, assigns: assigns}} <- named_slots do
+      for {name, %{slot: slot, parameter: parameter, attributes: attributes}} <- named_slots do
         Temple.Ast.new(
-          Temple.Parser.Slottable,
+          Temple.Ast.Slottable,
           name: name,
           content: Temple.Parser.parse(slot),
-          assigns: assigns
+          parameter: parameter,
+          attributes: attributes
         )
       end
 
+    slots = children ++ slots
+
     Temple.Ast.new(__MODULE__,
       function: component_function,
-      assigns: assigns,
-      slots: slots,
-      children: children
+      arguments: arguments,
+      slots: slots
     )
   end
 
   defp split_assigns_and_children(args, empty) do
     case args do
-      [assigns, [do: block]] ->
-        {assigns, block}
+      [arguments, [do: block]] ->
+        {arguments, block}
 
       [[do: block]] ->
         {empty, block}
 
-      [assigns] ->
-        {assigns, nil}
+      [arguments] ->
+        {arguments, nil}
 
       _ ->
         {empty, nil}
diff --git a/lib/temple/parser/default.ex b/lib/temple/ast/default.ex
similarity index 88%
rename from lib/temple/parser/default.ex
rename to lib/temple/ast/default.ex
index 39f481a..a605dea 100644
--- a/lib/temple/parser/default.ex
+++ b/lib/temple/ast/default.ex
@@ -1,4 +1,4 @@
-defmodule Temple.Parser.Default do
+defmodule Temple.Ast.Default do
   @moduledoc false
   @behaviour Temple.Parser
 
diff --git a/lib/temple/parser/do_expressions.ex b/lib/temple/ast/do_expressions.ex
similarity index 86%
rename from lib/temple/parser/do_expressions.ex
rename to lib/temple/ast/do_expressions.ex
index ebad04f..ab1af30 100644
--- a/lib/temple/parser/do_expressions.ex
+++ b/lib/temple/ast/do_expressions.ex
@@ -1,4 +1,4 @@
-defmodule Temple.Parser.DoExpressions do
+defmodule Temple.Ast.DoExpressions do
   @moduledoc false
   @behaviour Temple.Parser
 
@@ -18,7 +18,7 @@ defmodule Temple.Parser.DoExpressions do
 
   @impl true
   def run({name, meta, args}) do
-    {do_and_else, args} = Temple.Parser.Utils.split_args(args)
+    {do_and_else, args} = Temple.Ast.Utils.split_args(args)
 
     do_body = Temple.Parser.parse(do_and_else[:do])
 
diff --git a/lib/temple/parser/element_list.ex b/lib/temple/ast/element_list.ex
similarity index 90%
rename from lib/temple/parser/element_list.ex
rename to lib/temple/ast/element_list.ex
index ecdcbf1..0c583a2 100644
--- a/lib/temple/parser/element_list.ex
+++ b/lib/temple/ast/element_list.ex
@@ -1,4 +1,4 @@
-defmodule Temple.Parser.ElementList do
+defmodule Temple.Ast.ElementList do
   @moduledoc false
 
   @behaviour Temple.Parser
diff --git a/lib/temple/parser/empty.ex b/lib/temple/ast/empty.ex
similarity index 88%
rename from lib/temple/parser/empty.ex
rename to lib/temple/ast/empty.ex
index 25dd8d3..a1ac2af 100644
--- a/lib/temple/parser/empty.ex
+++ b/lib/temple/ast/empty.ex
@@ -1,4 +1,4 @@
-defmodule Temple.Parser.Empty do
+defmodule Temple.Ast.Empty do
   @moduledoc false
 
   use TypedStruct
diff --git a/lib/temple/parser/match.ex b/lib/temple/ast/match.ex
similarity index 90%
rename from lib/temple/parser/match.ex
rename to lib/temple/ast/match.ex
index 1f6e0d5..c180488 100644
--- a/lib/temple/parser/match.ex
+++ b/lib/temple/ast/match.ex
@@ -1,4 +1,4 @@
-defmodule Temple.Parser.Match do
+defmodule Temple.Ast.Match do
   @moduledoc false
   @behaviour Temple.Parser
 
diff --git a/lib/temple/parser/nonvoid_elements_aliases.ex b/lib/temple/ast/nonvoid_elements_aliases.ex
similarity index 78%
rename from lib/temple/parser/nonvoid_elements_aliases.ex
rename to lib/temple/ast/nonvoid_elements_aliases.ex
index 76a1a98..5237b51 100644
--- a/lib/temple/parser/nonvoid_elements_aliases.ex
+++ b/lib/temple/ast/nonvoid_elements_aliases.ex
@@ -1,4 +1,4 @@
-defmodule Temple.Parser.NonvoidElementsAliases do
+defmodule Temple.Ast.NonvoidElementsAliases do
   @moduledoc false
   @behaviour Temple.Parser
 
@@ -24,9 +24,9 @@ defmodule Temple.Parser.NonvoidElementsAliases do
   def run({name, meta, args}) do
     name = Parser.nonvoid_elements_lookup()[name]
 
-    {do_and_else, args} = Temple.Parser.Utils.split_args(args)
+    {do_and_else, args} = Temple.Ast.Utils.split_args(args)
 
-    {do_and_else, args} = Temple.Parser.Utils.consolidate_blocks(do_and_else, args)
+    {do_and_else, args} = Temple.Ast.Utils.consolidate_blocks(do_and_else, args)
 
     children = Temple.Parser.parse(do_and_else[:do])
 
@@ -35,7 +35,7 @@ defmodule Temple.Parser.NonvoidElementsAliases do
       attrs: args,
       meta: %{whitespace: whitespace(meta)},
       children:
-        Temple.Ast.new(Temple.Parser.ElementList,
+        Temple.Ast.new(Temple.Ast.ElementList,
           children: children,
           whitespace: whitespace(meta)
         )
diff --git a/lib/temple/parser/right_arrow.ex b/lib/temple/ast/right_arrow.ex
similarity index 92%
rename from lib/temple/parser/right_arrow.ex
rename to lib/temple/ast/right_arrow.ex
index 1e7e8e9..9f6785e 100644
--- a/lib/temple/parser/right_arrow.ex
+++ b/lib/temple/ast/right_arrow.ex
@@ -1,4 +1,4 @@
-defmodule Temple.Parser.RightArrow do
+defmodule Temple.Ast.RightArrow do
   @moduledoc false
 
   @behaviour Temple.Parser
diff --git a/lib/temple/parser/slot.ex b/lib/temple/ast/slot.ex
similarity index 93%
rename from lib/temple/parser/slot.ex
rename to lib/temple/ast/slot.ex
index f513fdc..458de65 100644
--- a/lib/temple/parser/slot.ex
+++ b/lib/temple/ast/slot.ex
@@ -1,4 +1,4 @@
-defmodule Temple.Parser.Slot do
+defmodule Temple.Ast.Slot do
   @moduledoc false
   @behaviour Temple.Parser
   use TypedStruct
diff --git a/lib/temple/ast/slottable.ex b/lib/temple/ast/slottable.ex
new file mode 100644
index 0000000..7337096
--- /dev/null
+++ b/lib/temple/ast/slottable.ex
@@ -0,0 +1,12 @@
+defmodule Temple.Ast.Slottable do
+  @moduledoc false
+
+  use TypedStruct
+
+  typedstruct do
+    field :content, [Temple.Ast.t()]
+    field :parameter, Macro.t()
+    field :name, atom()
+    field :attributes, Macro.t(), default: []
+  end
+end
diff --git a/lib/temple/parser/temple_namespace_nonvoid.ex b/lib/temple/ast/temple_namespace_nonvoid.ex
similarity index 76%
rename from lib/temple/parser/temple_namespace_nonvoid.ex
rename to lib/temple/ast/temple_namespace_nonvoid.ex
index 1bb2bcc..4f4e7e8 100644
--- a/lib/temple/parser/temple_namespace_nonvoid.ex
+++ b/lib/temple/ast/temple_namespace_nonvoid.ex
@@ -1,4 +1,4 @@
-defmodule Temple.Parser.TempleNamespaceNonvoid do
+defmodule Temple.Ast.TempleNamespaceNonvoid do
   @moduledoc false
   @behaviour Temple.Parser
 
@@ -14,6 +14,6 @@ defmodule Temple.Parser.TempleNamespaceNonvoid do
   @impl true
   def run({name, meta, args}) do
     {:., _, [{:__aliases__, _, [:Temple]}, name]} = name
-    Temple.Parser.NonvoidElementsAliases.run({name, meta, args})
+    Temple.Ast.NonvoidElementsAliases.run({name, meta, args})
   end
 end
diff --git a/lib/temple/parser/temple_namespace_void.ex b/lib/temple/ast/temple_namespace_void.ex
similarity index 76%
rename from lib/temple/parser/temple_namespace_void.ex
rename to lib/temple/ast/temple_namespace_void.ex
index 2545f40..4262c3d 100644
--- a/lib/temple/parser/temple_namespace_void.ex
+++ b/lib/temple/ast/temple_namespace_void.ex
@@ -1,4 +1,4 @@
-defmodule Temple.Parser.TempleNamespaceVoid do
+defmodule Temple.Ast.TempleNamespaceVoid do
   @moduledoc false
   @behaviour Temple.Parser
 
@@ -13,6 +13,6 @@ defmodule Temple.Parser.TempleNamespaceVoid do
   def run({name, meta, args}) do
     {:., _, [{:__aliases__, _, [:Temple]}, name]} = name
 
-    Temple.Parser.VoidElementsAliases.run({name, meta, args})
+    Temple.Ast.VoidElementsAliases.run({name, meta, args})
   end
 end
diff --git a/lib/temple/parser/text.ex b/lib/temple/ast/text.ex
similarity index 90%
rename from lib/temple/parser/text.ex
rename to lib/temple/ast/text.ex
index 0d7cc65..bd2a14d 100644
--- a/lib/temple/parser/text.ex
+++ b/lib/temple/ast/text.ex
@@ -1,4 +1,4 @@
-defmodule Temple.Parser.Text do
+defmodule Temple.Ast.Text do
   @moduledoc false
   @behaviour Temple.Parser
 
diff --git a/lib/temple/parser/utils.ex b/lib/temple/ast/utils.ex
similarity index 97%
rename from lib/temple/parser/utils.ex
rename to lib/temple/ast/utils.ex
index cae7523..a1513d2 100644
--- a/lib/temple/parser/utils.ex
+++ b/lib/temple/ast/utils.ex
@@ -1,4 +1,4 @@
-defmodule Temple.Parser.Utils do
+defmodule Temple.Ast.Utils do
   @moduledoc false
 
   def snake_to_kebab(stringable),
@@ -25,7 +25,7 @@ defmodule Temple.Parser.Utils do
             [{:text, " " <> name <> "=\"" <> to_string(value) <> "\""} | acc]
           else
             true ->
-              nodes = Temple.Parser.Utils.build_attr(name, value)
+              nodes = Temple.Ast.Utils.build_attr(name, value)
               Enum.reverse(nodes) ++ acc
           end
       end
diff --git a/lib/temple/parser/void_elements_aliases.ex b/lib/temple/ast/void_elements_aliases.ex
similarity index 85%
rename from lib/temple/parser/void_elements_aliases.ex
rename to lib/temple/ast/void_elements_aliases.ex
index 7ba4d96..e3f954d 100644
--- a/lib/temple/parser/void_elements_aliases.ex
+++ b/lib/temple/ast/void_elements_aliases.ex
@@ -1,4 +1,4 @@
-defmodule Temple.Parser.VoidElementsAliases do
+defmodule Temple.Ast.VoidElementsAliases do
   @moduledoc false
   @behaviour Temple.Parser
 
@@ -19,7 +19,7 @@ defmodule Temple.Parser.VoidElementsAliases do
   @impl true
   def run({name, _, args}) do
     args =
-      case Temple.Parser.Utils.split_args(args) do
+      case Temple.Ast.Utils.split_args(args) do
         {_, [args]} when is_list(args) ->
           args
 
diff --git a/lib/temple/parser.ex b/lib/temple/parser.ex
index 0046824..f72a246 100644
--- a/lib/temple/parser.ex
+++ b/lib/temple/parser.ex
@@ -1,19 +1,19 @@
 defmodule Temple.Parser do
   @moduledoc false
 
-  alias Temple.Parser.AnonymousFunctions
-  alias Temple.Parser.Components
-  alias Temple.Parser.Default
-  alias Temple.Parser.DoExpressions
-  alias Temple.Parser.Empty
-  alias Temple.Parser.Match
-  alias Temple.Parser.NonvoidElementsAliases
-  alias Temple.Parser.RightArrow
-  alias Temple.Parser.Slot
-  alias Temple.Parser.TempleNamespaceNonvoid
-  alias Temple.Parser.TempleNamespaceVoid
-  alias Temple.Parser.Text
-  alias Temple.Parser.VoidElementsAliases
+  alias Temple.Ast.AnonymousFunctions
+  alias Temple.Ast.Components
+  alias Temple.Ast.Default
+  alias Temple.Ast.DoExpressions
+  alias Temple.Ast.Empty
+  alias Temple.Ast.Match
+  alias Temple.Ast.NonvoidElementsAliases
+  alias Temple.Ast.RightArrow
+  alias Temple.Ast.Slot
+  alias Temple.Ast.TempleNamespaceNonvoid
+  alias Temple.Ast.TempleNamespaceVoid
+  alias Temple.Ast.Text
+  alias Temple.Ast.VoidElementsAliases
 
   @aliases Application.compile_env(:temple, :aliases, [])
 
diff --git a/lib/temple/parser/slottable.ex b/lib/temple/parser/slottable.ex
deleted file mode 100644
index 3c32485..0000000
--- a/lib/temple/parser/slottable.ex
+++ /dev/null
@@ -1,11 +0,0 @@
-defmodule Temple.Parser.Slottable do
-  @moduledoc false
-
-  use TypedStruct
-
-  typedstruct do
-    field :content, any()
-    field :assigns, map(), default: Macro.escape(%{})
-    field :name, atom()
-  end
-end
diff --git a/lib/temple/renderer.ex b/lib/temple/renderer.ex
index 4538dc0..94dbf04 100644
--- a/lib/temple/renderer.ex
+++ b/lib/temple/renderer.ex
@@ -1,20 +1,21 @@
 defmodule Temple.Renderer do
   @moduledoc false
 
-  alias Temple.Parser.ElementList
-  alias Temple.Parser.Text
-  alias Temple.Parser.Components
-  alias Temple.Parser.Slot
-  alias Temple.Parser.NonvoidElementsAliases
-  alias Temple.Parser.VoidElementsAliases
-  alias Temple.Parser.AnonymousFunctions
-  alias Temple.Parser.RightArrow
-  alias Temple.Parser.DoExpressions
-  alias Temple.Parser.Match
-  alias Temple.Parser.Default
-  alias Temple.Parser.Empty
-
-  alias Temple.Parser.Utils
+  alias Temple.Ast.ElementList
+  alias Temple.Ast.Text
+  alias Temple.Ast.Components
+  alias Temple.Ast.Slot
+  alias Temple.Ast.Slottable
+  alias Temple.Ast.NonvoidElementsAliases
+  alias Temple.Ast.VoidElementsAliases
+  alias Temple.Ast.AnonymousFunctions
+  alias Temple.Ast.RightArrow
+  alias Temple.Ast.DoExpressions
+  alias Temple.Ast.Match
+  alias Temple.Ast.Default
+  alias Temple.Ast.Empty
+
+  alias Temple.Ast.Utils
 
   @default_engine EEx.SmartEngine
 
@@ -23,7 +24,7 @@ defmodule Temple.Renderer do
     |> Temple.Parser.parse()
     |> Temple.Renderer.render(opts)
 
-    # |> Temple.Parser.Utils.inspect_ast()
+    # |> Temple.Ast.Utils.inspect_ast()
   end
 
   def render(asts, opts \\ [])
@@ -37,7 +38,7 @@ defmodule Temple.Renderer do
       terminal_node: false
     }
 
-    buffer = engine.init(%{})
+    buffer = engine.init([])
 
     buffer =
       for ast <- asts, reduce: buffer do
@@ -73,25 +74,11 @@ defmodule Temple.Renderer do
 
   def render(buffer, state, %Components{
         function: function,
-        assigns: assigns,
-        children: children,
+        arguments: arguments,
         slots: slots
       }) do
-    child_quoted =
-      if Enum.any?(children) do
-        children_buffer = state.engine.handle_begin(buffer)
-
-        children_buffer =
-          for child <- children(children), reduce: children_buffer do
-            children_buffer ->
-              render(children_buffer, state, child)
-          end
-
-        state.engine.handle_end(children_buffer)
-      end
-
     slot_quotes =
-      for slot <- slots do
+      Enum.group_by(slots, & &1.name, fn %Slottable{} = slot ->
         slot_buffer = state.engine.handle_begin(buffer)
 
         slot_buffer =
@@ -102,29 +89,34 @@ defmodule Temple.Renderer do
 
         ast = state.engine.handle_end(slot_buffer)
 
-        [quoted] =
+        inner_block =
           quote do
-            {unquote(slot.name), unquote(slot.assigns)} ->
-              unquote(ast)
+            inner_block unquote(slot.name) do
+              unquote(slot.parameter || quote(do: _)) ->
+                unquote(ast)
+            end
           end
 
-        quoted
-      end
-
-    {:fn, meta, clauses} =
-      quote do
-        fn
-          {:default, _} -> unquote(child_quoted)
-        end
-      end
+        {:%{}, [],
+         [
+           __slot__: slot.name,
+           inner_block: inner_block
+         ] ++ slot.attributes}
+      end)
 
-    slot_func = {:fn, meta, slot_quotes ++ clauses}
+    component_arguments =
+      {:%{}, [],
+       arguments
+       |> Map.new()
+       |> Map.merge(slot_quotes)
+       |> Enum.to_list()}
 
     expr =
       quote do
         component(
           unquote(function),
-          Map.put(Map.new(unquote(assigns)), :__slots__, unquote(slot_func))
+          unquote(component_arguments),
+          {__MODULE__, __ENV__.function, __ENV__.file, __ENV__.line}
         )
       end
 
@@ -134,7 +126,7 @@ defmodule Temple.Renderer do
   def render(buffer, state, %Slot{} = ast) do
     render_slot_func =
       quote do
-        var!(assigns).__slots__.({unquote(ast.name), Map.new(unquote(ast.args))})
+        render_slot(unquote(ast.name), unquote(ast.args))
       end
 
     state.engine.handle_expr(buffer, "=", render_slot_func)
@@ -242,7 +234,7 @@ defmodule Temple.Renderer do
     {name, meta, args} = ast.elixir_ast
 
     {args, {func, fmeta, [{arrow, arrowmeta, [first, _block]}]}, args2} =
-      Temple.Parser.Utils.split_on_fn(args, {[], nil, []})
+      Temple.Ast.Utils.split_on_fn(args, {[], nil, []})
 
     full_ast =
       {name, meta, args ++ [{func, fmeta, [{arrow, arrowmeta, [first, inner_quoted]}]}] ++ args2}


Test patch:
diff --git a/test/parser/anonymous_functions_test.exs b/test/parser/anonymous_functions_test.exs
index d100711..f335e60 100644
--- a/test/parser/anonymous_functions_test.exs
+++ b/test/parser/anonymous_functions_test.exs
@@ -1,7 +1,7 @@
-defmodule Temple.Parser.AnonymousFunctionsTest do
+defmodule Temple.Ast.AnonymousFunctionsTest do
   use ExUnit.Case, async: true
 
-  alias Temple.Parser.AnonymousFunctions
+  alias Temple.Ast.AnonymousFunctions
 
   describe "applicable?/1" do
     test "returns true when the node contains an anonymous function as an argument to a function" do
@@ -57,7 +57,7 @@ defmodule Temple.Parser.AnonymousFunctionsTest do
       assert %AnonymousFunctions{
                elixir_ast: _,
                children: [
-                 %Temple.Parser.Default{
+                 %Temple.Ast.Default{
                    elixir_ast: ^expected_child
                  }
                ]
diff --git a/test/parser/components_test.exs b/test/parser/components_test.exs
index 88a1754..89d200a 100644
--- a/test/parser/components_test.exs
+++ b/test/parser/components_test.exs
@@ -1,7 +1,7 @@
-defmodule Temple.Parser.ComponentsTest do
-  use ExUnit.Case, async: false
-  alias Temple.Parser.Components
-  alias Temple.Parser.Slottable
+defmodule Temple.Ast.ComponentsTest do
+  use ExUnit.Case, async: true
+  alias Temple.Ast.Components
+  alias Temple.Ast.Slottable
 
   describe "applicable?/1" do
     test "runs when using the `c` ast with a block" do
@@ -57,8 +57,7 @@ defmodule Temple.Parser.ComponentsTest do
 
       assert %Components{
                function: ^func,
-               assigns: [],
-               children: _
+               arguments: []
              } = ast
     end
 
@@ -72,8 +71,7 @@ defmodule Temple.Parser.ComponentsTest do
 
       assert %Components{
                function: ^func,
-               assigns: [foo: :bar],
-               children: _
+               arguments: [foo: :bar]
              } = ast
     end
 
@@ -91,8 +89,7 @@ defmodule Temple.Parser.ComponentsTest do
 
       assert %Components{
                function: ^func,
-               assigns: [foo: :bar],
-               children: _
+               arguments: [foo: :bar]
              } = ast
     end
 
@@ -106,8 +103,7 @@ defmodule Temple.Parser.ComponentsTest do
 
       assert %Components{
                function: ^func,
-               assigns: [foo: :bar],
-               children: []
+               arguments: [foo: :bar]
              } = ast
     end
 
@@ -115,7 +111,7 @@ defmodule Temple.Parser.ComponentsTest do
       raw_ast =
         quote do
           c unquote(func), foo: :bar do
-            slot :foo, %{form: form} do
+            slot :foo, let: %{form: form} do
               "in the slot"
             end
           end
@@ -125,15 +121,40 @@ defmodule Temple.Parser.ComponentsTest do
 
       assert %Components{
                function: ^func,
-               assigns: [foo: :bar],
+               arguments: [foo: :bar],
                slots: [
                  %Slottable{
                    name: :foo,
-                   content: [%Temple.Parser.Text{}],
-                   assigns: {:%{}, _, [form: _]}
+                   content: [%Temple.Ast.Text{}],
+                   parameter: {:%{}, _, [form: _]}
                  }
-               ],
-               children: []
+               ]
+             } = ast
+    end
+
+    test "slot attributes", %{func: func} do
+      raw_ast =
+        quote do
+          c unquote(func), foo: :bar do
+            slot :foo, let: %{form: form}, label: the_label do
+              "in the slot"
+            end
+          end
+        end
+
+      ast = Components.run(raw_ast)
+
+      assert %Components{
+               function: ^func,
+               arguments: [foo: :bar],
+               slots: [
+                 %Slottable{
+                   name: :foo,
+                   content: [%Temple.Ast.Text{}],
+                   parameter: {:%{}, _, [form: _]},
+                   attributes: [label: {:the_label, [], Temple.Ast.ComponentsTest}]
+                 }
+               ]
              } = ast
     end
 
@@ -149,7 +170,7 @@ defmodule Temple.Parser.ComponentsTest do
               c unquote(list), socials: @user.socials do
                 "hello"
 
-                slot :default, %{text: text, url: url} do
+                slot :foo, let: %{text: text, url: url} do
                   a class: "text-blue-500 hover:underline", href: url do
                     text
                   end
@@ -161,16 +182,32 @@ defmodule Temple.Parser.ComponentsTest do
 
       ast = Components.run(raw_ast)
 
-      assert Kernel.==(ast.slots, [])
+      assert [
+               %Slottable{
+                 name: :inner_block,
+                 parameter: nil
+               }
+             ] = ast.slots
 
       assert %Components{
-               children: [
-                 %Components{
-                   children: [
+               slots: [
+                 %Slottable{
+                   content: [
                      %Components{
                        slots: [
                          %Slottable{
-                           name: :default
+                           content: [
+                             %Components{
+                               slots: [
+                                 %Slottable{
+                                   name: :inner_block
+                                 },
+                                 %Slottable{
+                                   name: :foo
+                                 }
+                               ]
+                             }
+                           ]
                          }
                        ]
                      }
diff --git a/test/parser/default_test.exs b/test/parser/default_test.exs
index 3f2aa99..3023dc2 100644
--- a/test/parser/default_test.exs
+++ b/test/parser/default_test.exs
@@ -1,7 +1,7 @@
-defmodule Temple.Parser.DefaultTest do
+defmodule Temple.Ast.DefaultTest do
   use ExUnit.Case, async: true
 
-  alias Temple.Parser.Default
+  alias Temple.Ast.Default
 
   describe "applicable?/1" do
     test "returns true when the node is an elixir expression" do
diff --git a/test/parser/do_expressions_test.exs b/test/parser/do_expressions_test.exs
index 7bde960..b4a5b30 100644
--- a/test/parser/do_expressions_test.exs
+++ b/test/parser/do_expressions_test.exs
@@ -1,7 +1,7 @@
-defmodule Temple.Parser.DoExpressionsTest do
+defmodule Temple.Ast.DoExpressionsTest do
   use ExUnit.Case, async: true
 
-  alias Temple.Parser.DoExpressions
+  alias Temple.Ast.DoExpressions
 
   describe "applicable?/1" do
     test "returns true when the node contains a do expression" do
@@ -30,7 +30,7 @@ defmodule Temple.Parser.DoExpressionsTest do
       assert %DoExpressions{
                elixir_ast: _,
                children: [
-                 [%Temple.Parser.Text{text: "bob"}],
+                 [%Temple.Ast.Text{text: "bob"}],
                  nil
                ]
              } = ast
diff --git a/test/parser/empty_test.exs b/test/parser/empty_test.exs
index b727786..a9128e1 100644
--- a/test/parser/empty_test.exs
+++ b/test/parser/empty_test.exs
@@ -1,7 +1,7 @@
-defmodule Temple.Parser.EmptyTest do
+defmodule Temple.Ast.EmptyTest do
   use ExUnit.Case, async: true
 
-  alias Temple.Parser.Empty
+  alias Temple.Ast.Empty
 
   describe "applicable?/1" do
     test "returns true when the node is non-content" do
diff --git a/test/parser/match_test.exs b/test/parser/match_test.exs
index 10bad4f..41320bf 100644
--- a/test/parser/match_test.exs
+++ b/test/parser/match_test.exs
@@ -1,7 +1,7 @@
-defmodule Temple.Parser.MatchTest do
+defmodule Temple.Ast.MatchTest do
   use ExUnit.Case, async: true
 
-  alias Temple.Parser.Match
+  alias Temple.Ast.Match
 
   describe "applicable?/1" do
     test "returns true when the node is an elixir match expression" do
diff --git a/test/parser/nonvoid_elements_aliases_test.exs b/test/parser/nonvoid_elements_aliases_test.exs
index 7965d03..6105cbf 100644
--- a/test/parser/nonvoid_elements_aliases_test.exs
+++ b/test/parser/nonvoid_elements_aliases_test.exs
@@ -1,8 +1,9 @@
-defmodule Temple.Parser.NonvoidElementsAliasesTest do
+defmodule Temple.Ast.NonvoidElementsAliasesTest do
   use ExUnit.Case, async: true
 
-  alias Temple.Parser.NonvoidElementsAliases
-  alias Temple.Parser.ElementList
+  alias Temple.Ast.NonvoidElementsAliases
+  alias Temple.Ast.ElementList
+  alias Temple.Ast.Text
 
   describe "applicable?/1" do
     test "returns true when the node is a nonvoid element or alias" do
@@ -74,7 +75,7 @@ defmodule Temple.Parser.NonvoidElementsAliasesTest do
                            name: "option",
                            children: %ElementList{
                              children: [
-                               %Temple.Parser.Text{text: "foo"}
+                               %Text{text: "foo"}
                              ]
                            }
                          }
diff --git a/test/parser/right_arrow_test.exs b/test/parser/right_arrow_test.exs
index 2c4dc4d..a62427d 100644
--- a/test/parser/right_arrow_test.exs
+++ b/test/parser/right_arrow_test.exs
@@ -1,7 +1,7 @@
-defmodule Temple.Parser.RightArrowTest do
+defmodule Temple.Ast.RightArrowTest do
   use ExUnit.Case, async: true
 
-  alias Temple.Parser.RightArrow
+  alias Temple.Ast.RightArrow
 
   describe "applicable?/1" do
     test "returns true when the node contains a right arrow" do
@@ -52,7 +52,7 @@ defmodule Temple.Parser.RightArrowTest do
       assert %RightArrow{
                elixir_ast: {:->, [newlines: 1], [[:bing]]},
                children: [
-                 %Temple.Parser.Default{
+                 %Temple.Ast.Default{
                    elixir_ast: ^bong
                  }
                ]
diff --git a/test/parser/slot_test.exs b/test/parser/slot_test.exs
index 9a88e4c..881c81d 100644
--- a/test/parser/slot_test.exs
+++ b/test/parser/slot_test.exs
@@ -1,6 +1,6 @@
-defmodule Temple.Parser.SlotTest do
-  use ExUnit.Case, async: false
-  alias Temple.Parser.Slot
+defmodule Temple.Ast.SlotTest do
+  use ExUnit.Case, async: true
+  alias Temple.Ast.Slot
 
   describe "applicable?/1" do
     test "runs when using the `c` ast with a block" do
diff --git a/test/parser/temple_namespace_nonvoid_test.exs b/test/parser/temple_namespace_nonvoid_test.exs
index 9d72d5e..83a348a 100644
--- a/test/parser/temple_namespace_nonvoid_test.exs
+++ b/test/parser/temple_namespace_nonvoid_test.exs
@@ -1,8 +1,10 @@
-defmodule Temple.Parser.TempleNamespaceNonvoidTest do
+defmodule Temple.Ast.TempleNamespaceNonvoidTest do
   use ExUnit.Case, async: true
 
-  alias Temple.Parser.NonvoidElementsAliases
-  alias Temple.Parser.TempleNamespaceNonvoid
+  alias Temple.Ast.ElementList
+  alias Temple.Ast.NonvoidElementsAliases
+  alias Temple.Ast.TempleNamespaceNonvoid
+  alias Temple.Ast.Text
 
   describe "applicable?/1" do
     test "returns true when the node is a Temple aliased nonvoid element" do
@@ -50,8 +52,8 @@ defmodule Temple.Parser.TempleNamespaceNonvoidTest do
       assert %NonvoidElementsAliases{
                name: "div",
                attrs: [class: "foo", id: {:var, [], _}],
-               children: %Temple.Parser.ElementList{
-                 children: [%Temple.Parser.Text{text: "foo"}],
+               children: %ElementList{
+                 children: [%Text{text: "foo"}],
                  whitespace: :loose
                }
              } = ast
diff --git a/test/parser/temple_namespace_void_test.exs b/test/parser/temple_namespace_void_test.exs
index d848f4b..5824c63 100644
--- a/test/parser/temple_namespace_void_test.exs
+++ b/test/parser/temple_namespace_void_test.exs
@@ -1,8 +1,8 @@
-defmodule Temple.Parser.TempleNamespaceVoidTest do
+defmodule Temple.Ast.TempleNamespaceVoidTest do
   use ExUnit.Case, async: true
 
-  alias Temple.Parser.TempleNamespaceVoid
-  alias Temple.Parser.VoidElementsAliases
+  alias Temple.Ast.TempleNamespaceVoid
+  alias Temple.Ast.VoidElementsAliases
 
   describe "applicable?/1" do
     test "returns true when the node is a Temple aliased nonvoid element" do
diff --git a/test/parser/text_test.exs b/test/parser/text_test.exs
index 39f207a..959e025 100644
--- a/test/parser/text_test.exs
+++ b/test/parser/text_test.exs
@@ -1,7 +1,7 @@
-defmodule Temple.Parser.TextTest do
+defmodule Temple.Ast.TextTest do
   use ExUnit.Case, async: true
 
-  alias Temple.Parser.Text
+  alias Temple.Ast.Text
 
   describe "applicable?/1" do
     test "returns true when the node is a string literal" do
diff --git a/test/parser/utils_test.exs b/test/parser/utils_test.exs
index 1d9c93e..f03b374 100644
--- a/test/parser/utils_test.exs
+++ b/test/parser/utils_test.exs
@@ -1,7 +1,7 @@
-defmodule Temple.Parser.UtilsTest do
+defmodule Temple.Ast.UtilsTest do
   use ExUnit.Case, async: true
 
-  alias Temple.Parser.Utils
+  alias Temple.Ast.Utils
 
   describe "compile_attrs/1" do
     test "returns a list of text nodes for static attributes" do
diff --git a/test/parser/void_elements_aliases_test.exs b/test/parser/void_elements_aliases_test.exs
index 68c4747..801d030 100644
--- a/test/parser/void_elements_aliases_test.exs
+++ b/test/parser/void_elements_aliases_test.exs
@@ -1,7 +1,7 @@
-defmodule Temple.Parser.VoidElementsAliasesTest do
+defmodule Temple.Ast.VoidElementsAliasesTest do
   use ExUnit.Case, async: true
 
-  alias Temple.Parser.VoidElementsAliases
+  alias Temple.Ast.VoidElementsAliases
 
   describe "applicable?/1" do
     test "returns true when the node is a nonvoid element or alias" do
diff --git a/test/temple/renderer_test.exs b/test/temple/renderer_test.exs
index 86dfb97..74a87ff 100644
--- a/test/temple/renderer_test.exs
+++ b/test/temple/renderer_test.exs
@@ -368,12 +368,14 @@ defmodule Temple.RendererTest do
       temple do
         div do
           "I am above the slot"
-          slot :default
+          slot @inner_block
         end
       end
     end
 
     test "component with default slot" do
+      assigns = %{}
+
       result =
         Renderer.compile do
           div do
@@ -404,23 +406,28 @@ defmodule Temple.RendererTest do
       temple do
         div do
           "#{@name} is above the slot"
-          slot :default
+          slot @inner_block
         end
 
         footer do
-          slot :footer, %{name: @name}
+          for f <- @footer do
+            span do: f[:label]
+            slot f, %{name: @name}
+          end
         end
       end
     end
 
     test "component with a named slot" do
+      assigns = %{label: "i'm a slot attribute"}
+
       result =
         Renderer.compile do
           div do
             c &named_slot/1, name: "motchy boi" do
               span do: "i'm a slot"
 
-              slot :footer, %{name: name} do
+              slot :footer, let: %{name: name}, label: @label, expr: 1 + 1 do
                 p do
                   "#{name}'s in the footer!"
                 end
@@ -439,6 +446,7 @@ defmodule Temple.RendererTest do
       </div>
 
       <footer>
+        <span>i'm a slot attribute</span>
         <p>
           motchy boi's in the footer!
         </p>
@@ -501,5 +509,58 @@ defmodule Temple.RendererTest do
 
       assert expected == result
     end
+
+    test "multiple slots" do
+      assigns = %{}
+
+      result =
+        Renderer.compile do
+          div do
+            c &named_slot/1, name: "motchy boi" do
+              span do: "i'm a slot"
+
+              slot :footer, let: %{name: name} do
+                p do
+                  "#{name}'s in the footer!"
+                end
+              end
+
+              slot :footer, let: %{name: name} do
+                p do
+                  "#{name} is the second footer!"
+                end
+              end
+            end
+          end
+        end
+
+      # heex
+      expected = """
+      <div>
+      <div>
+        motchy boi is above the slot
+        <span>i'm a slot</span>
+
+      </div>
+
+      <footer>
+        <span></span>
+        <p>
+          motchy boi's in the footer!
+        </p>
+        <span></span>
+        <p>
+          motchy boi is the second footer!
+        </p>
+
+      </footer>
+
+
+      </div>
+
+      """
+
+      assert expected == result
+    end
   end
 end