Skip to content

Commit

Permalink
Refactored LiveViewNative.Component
Browse files Browse the repository at this point in the history
This simplifies the macro and fixees some nagging bugs with components
that weren't properly passing through our compiler
  • Loading branch information
bcardarella committed Jan 10, 2025
1 parent 2dd41d5 commit c8cc6a4
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 191 deletions.
221 changes: 113 additions & 108 deletions lib/live_view_native/component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -111,137 +111,88 @@ defmodule LiveViewNative.Component do
'''
defmacro __using__(opts) do
%{module: module} = __CALLER__

{opts, _} = Code.eval_quoted(opts)

format = opts[:format]

plugin = case LiveViewNative.fetch_plugin(format) do
{:ok, plugin} -> plugin
:error -> %{component: module}
end

declarative_opts = Keyword.drop(opts, [:as, :format, :root])
Module.put_attribute(module, :native_opts, %{
as: opts[:as],
format: format,
root: opts[:root]
})

declarative_opts = Keyword.drop(opts, [:as, :format, :root])

component_ast = quote do
import Phoenix.LiveView.Helpers
quote do
import Kernel, except: [def: 2, defp: 2]
unquote(if format == :html do
quote do: import Phoenix.Component
else
quote do
import Phoenix.Component, except: [
embed_templates: 1, embed_templates: 2,
sigil_H: 2,

async_result: 1,
dynamic_tag: 1,
focus_wrap: 1,
form: 1,
inputs_for: 1,
intersperse: 1,
link: 1,
live_file_input: 1,
live_img_preview: 1,
live_title: 1,
to_form: 1,
to_form: 2
]
end
end)

import Phoenix.Component.Declarative, only: []
import LiveViewNative.Component.Declarative
require Phoenix.Template

for {prefix_match, value} <- LiveViewNative.Component.Declarative.__setup__(__MODULE__, unquote(declarative_opts)) do
@doc false
def __global__?(prefix_match), do: value
end

def __native_opts__, do: @native_opts
end

components_ast = quote location: :keep do
unquote(if format != :html do
unquote(if format == :html do
quote do
@doc """
Please see the documentation for Phoenix.Component.async_result/1
"""
@doc type: :component
attr(:assign, Phoenix.LiveView.AsyncResult, required: true)
slot(:loading, doc: "rendered while the assign is loading for the first time")

slot(:failed,
doc:
"rendered when an error or exit is caught or assign_async returns `{:error, reason}` for the first time. Receives the error as a `:let`"
)

slot(:inner_block,
doc:
"rendered when the assign is loaded successfully via `AsyncResult.ok/2`. Receives the result as a `:let`"
)

def async_result(assigns, interface)
def async_result(%{assign: async_assign} = var!(assigns), _interface) do
cond do
async_assign.ok? ->
~LVN|{render_slot(@inner_block, @assign.result)}|

async_assign.loading ->
~LVN|{render_slot(@loading, @assign.loading)}|

async_assign.failed ->
~LVN|{render_slot(@failed, @assign.failed)}|
end
import Phoenix.Component
import PhoenixLiveView.Component.Declarative

for {prefix_match, value} <- Phoenix.LiveView.Component.Declarative.__setup__(__MODULE__, unquote(declarative_opts)) do
@doc false
def __global__?(prefix_match), do: value
end
end
else
quote do
import LiveViewNative.Component
import LiveViewNative.Component.Declarative
import Phoenix.Component, only: [
assign: 2, assign: 3,
assign_new: 3,
assigns_to_attributes: 2,
attr: 2, attr: 3,
changed?: 2,
live_flash: 2,
live_render: 3,
render_slot: 1, render_slot: 2,
update: 3,
upload_errors: 1,
upload_errors: 2,
used_input?: 1,
slot: 1, slot: 2, slot: 3
]

def live_component(assigns, _interface),
do: Phoenix.Component.live_component(assigns)
for {prefix_match, value} <- LiveViewNative.Component.Declarative.__setup__(__MODULE__, unquote(declarative_opts)) do
@doc false
def __global__?(prefix_match), do: value
end
end
end)
end

plugin_component_ast = plugin_component_ast(format, opts)

[component_ast, plugin_component_ast, components_ast]
end

defp plugin_component_ast(nil, _opts) do
quote do
import LiveViewNative.Component, only: [sigil_LVN: 2]
end
end

defp plugin_component_ast(format, opts) do
case LiveViewNative.fetch_plugin(format) do
{:ok, plugin} ->
quote do
Module.register_attribute(__MODULE__, :template_files, accumulate: true)
Module.register_attribute(__MODULE__, :embeded_templates_opts, accumulate: true)

import LiveViewNative.Renderer, only: [
delegate_to_target: 1,
delegate_to_target: 2,
embed_templates: 1,
embed_templates: 2
]
@doc false
def __native_opts__, do: @native_opts

use unquote(plugin.component)
Module.register_attribute(__MODULE__, :template_files, accumulate: true)
Module.register_attribute(__MODULE__, :embeded_templates_opts, accumulate: true)

if (unquote(opts[:as])) do
@before_compile LiveViewNative.Renderer
end
import LiveViewNative.Renderer, only: [
delegate_to_target: 1,
delegate_to_target: 2,
embed_templates: 1,
embed_templates: 2
]

@before_compile LiveViewNative.Component
@before_compile {LiveViewNative.Renderer, :__inject_mix_recompile__}
end
unquote(if (module == plugin.component) do
quote do: (import LiveViewNative.Component, only: [sigil_LVN: 2])
else
quote do: use unquote(plugin.component)
end)

:error ->
IO.warn("tried to load LiveViewNative plugin for format #{inspect(format)} but none was found")
if (unquote(opts[:as])) do
@before_compile LiveViewNative.Renderer
end

nil
@before_compile LiveViewNative.Component
@before_compile {LiveViewNative.Renderer, :__inject_mix_recompile__}
end
end

Expand Down Expand Up @@ -308,10 +259,64 @@ defmodule LiveViewNative.Component do
EEx.compile_string(expr, options)
end

import Kernel, except: [def: 2, defp: 2]
import LiveViewNative.Component.Declarative
alias LiveViewNative.Component.Declarative

# We need to bootstrap by hand to avoid conflicts.
[] = Declarative.__setup__(__MODULE__, [])

attr = fn name, type, opts ->
Declarative.__attr__!(__MODULE__, name, type, opts, __ENV__.line, __ENV__.file)
end

slot = fn name, opts ->
Declarative.__slot__!(__MODULE__, name, opts, __ENV__.line, __ENV__.file, fn -> nil end)
end

@doc """
Please see the documentation for Phoenix.Component.async_result/1
"""
@doc type: :component
attr.(:assign, Phoenix.LiveView.AsyncResult, required: true)
slot.(:loading, doc: "rendered while the assign is loading for the first time")

slot.(:failed,
doc:
"rendered when an error or exit is caught or assign_async returns `{:error, reason}` for the first time. Receives the error as a `:let`"
)

slot.(:inner_block,
doc:
"rendered when the assign is loaded successfully via `AsyncResult.ok/2`. Receives the result as a `:let`"
)

import Phoenix.Component, only: [
render_slot: 2
]

def async_result(%{assign: async_assign} = var!(assigns), _interface) do
cond do
async_assign.ok? ->
~LVN|{render_slot(@inner_block, @assign.result)}|

async_assign.loading ->
~LVN|{render_slot(@loading, @assign.loading)}|

async_assign.failed ->
~LVN|{render_slot(@failed, @assign.failed)}|
end
end

def live_component(assigns, _interface),
do: Phoenix.Component.live_component(assigns)

@doc """
Embed the CSRF token for LiveView as a tag
"""
def csrf_token(assigns) do
def csrf_token(assigns, _interface) do
IO.warn("csrf_token component has been deprecated. Please chance to raw dog markup: <csrf-token value={Phoenix.Controller.get_csrf_token()}/>")

csrf_token = Phoenix.Controller.get_csrf_token()

assigns = Map.put(assigns, :csrf_token, csrf_token)
Expand Down
2 changes: 1 addition & 1 deletion lib/live_view_native/component/declarative.ex
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,7 @@ defmodule LiveViewNative.Component.Declarative do

def_body =
if global_name do
quote location: :keep do
quote do
{assigns, caller_globals} = Map.split(assigns, unquote(known_keys))

globals =
Expand Down
12 changes: 6 additions & 6 deletions lib/live_view_native/renderer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ defmodule LiveViewNative.Renderer do
:error -> Path.join(file_path, Atom.to_string(opts[:format]))
end

quote location: :keep do
quote do
embed_templates(unquote(pattern), root: unquote(root), name: unquote(opts[:as]))
end
end
Expand Down Expand Up @@ -70,7 +70,7 @@ defmodule LiveViewNative.Renderer do

[]
else
quote location: :keep do
quote do
@doc false
def unquote(name)(var!(assigns)) do
interface = get_interface(var!(assigns))
Expand Down Expand Up @@ -113,7 +113,7 @@ defmodule LiveViewNative.Renderer do
root = Keyword.get(opts, :root, Path.dirname(file))
name = opts[:name]

attr_ast = quote location: :keep do
attr_ast = quote do
Module.put_attribute(__MODULE__, :embeded_templates_opts, {
unquote(root),
unquote(pattern),
Expand Down Expand Up @@ -225,7 +225,7 @@ defmodule LiveViewNative.Renderer do

case extract_target(template, format) do
nil ->
quote location: :keep do
quote do
@file unquote(template)
@external_resource unquote(template)
@template_files unquote(template)
Expand All @@ -236,7 +236,7 @@ defmodule LiveViewNative.Renderer do
end

target ->
quote location: :keep do
quote do
@file unquote(template)
@external_resource unquote(template)
@template_files unquote(template)
Expand All @@ -247,7 +247,7 @@ defmodule LiveViewNative.Renderer do
end
end
end)
|> List.insert_at(-1, quote location: :keep do
|> List.insert_at(-1, quote do
delegate_to_target unquote(name)
end)
end
Expand Down
4 changes: 2 additions & 2 deletions lib/live_view_native/template/engine.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ defmodule LiveViewNative.Template.Engine do
@doc false
@impl true
def compile(path, _name) do
quote location: :keep do
quote do
require LiveViewNative.Template.Engine
LiveViewNative.Template.Engine.compile(unquote(path))
end
Expand All @@ -19,7 +19,7 @@ defmodule LiveViewNative.Template.Engine do
source = File.read!(path)

EEx.compile_string(source,
engine: Phoenix.LiveView.TagEngine,
engine: LiveViewNative.TagEngine,
line: 1,
file: path,
trim: trim,
Expand Down
Loading

0 comments on commit c8cc6a4

Please sign in to comment.