Packages

Phoenix.React is use for renders React component as Phoenix Component in heex template. Support render_to_string and render_to_static_markup and cache render result. Only render to string support hyrate react component with phx-hook.

Current section

Files

Jump to
phoenix_react_server lib phoenix react server.ex
Raw

lib/phoenix/react/server.ex

defmodule Phoenix.React.Server do
@moduledoc """
The React Render Server
Start React Render server by setting.
```
config :phoenix_react_server, Phoenix.React,
runtime: Phoenix.React.Runtime.Bun,
component_base: Path.expand("../assets/component", __DIR__),
cache_ttl: 10
```
"""
require Logger
alias Phoenix.React.Cache
alias Phoenix.React.Runtime
use GenServer
@type second() :: integer()
@type millisecond() :: integer()
def set_runtime_process(pid) do
GenServer.cast(__MODULE__, {:set_runtime_process, pid})
end
@doc """
Return the configuration of the React Render Server from `Application.get_env(:phoenix_react_server, Phoenix.React)`
"""
@spec config() :: [
{:cache_ttl, second()}
| {:component_base, binary()}
| {:render_timeout, millisecond()}
| {:runtime, module()},
...
]
def config() do
config = Application.get_env(:phoenix_react_server, Phoenix.React)
[
runtime: config[:runtime] || Phoenix.React.Runtime.Bun,
component_base: config[:component_base],
cache_ttl: config[:cache_ttl] || 600,
render_timeout: config[:render_timeout] || 300_000
]
end
def start_link(init_arg) do
GenServer.start_link(__MODULE__, init_arg, name: __MODULE__)
end
@impl true
def init([]) do
cfg = config()
runtime = cfg[:runtime]
component_base = cfg[:component_base]
render_timeout = cfg[:render_timeout]
args = [component_base: component_base, render_timeout: render_timeout]
{:ok, runtime_process} = Runtime.start_runtime(runtime, args)
{:ok,
%{
runtime: runtime,
component_base: component_base,
render_timeout: render_timeout,
runtime_process: runtime_process
}}
end
@impl true
def handle_cast({:set_runtime_process, pid}, state) do
{:noreply, %{state | runtime_process: pid}}
end
@impl true
def handle_call(:get_state, _from, state) do
{:reply, state, state}
end
@impl true
def handle_call(
{:render_to_readable_stream, component, props},
_from,
%{runtime_process: runtime_process} = state
) do
reply =
case Cache.get(component, props, :render_to_readable_stream) do
nil ->
render_timeout = config()[:render_timeout]
case GenServer.call(
runtime_process,
{:render_to_readable_stream, component, props},
render_timeout
) do
{:ok, html} = reply ->
Cache.put(component, props, :render_to_readable_stream, html)
reply
reply ->
reply
end
html ->
{:ok, html}
end
{:reply, reply, state}
end
def handle_call(
{:render_to_string, component, props},
_from,
%{runtime_process: runtime_process} = state
) do
reply =
case Cache.get(component, props, :render_to_string) do
nil ->
render_timeout = config()[:render_timeout]
case GenServer.call(
runtime_process,
{:render_to_string, component, props},
render_timeout
) do
{:ok, html} = reply ->
Cache.put(component, props, :render_to_string, html)
reply
reply ->
reply
end
html ->
{:ok, html}
end
{:reply, reply, state}
end
def handle_call(
{:render_to_static_markup, component, props},
_from,
%{runtime_process: runtime_process} = state
) do
reply =
case Cache.get(component, props, :render_to_static_markup) do
nil ->
render_timeout = config()[:render_timeout]
case GenServer.call(
runtime_process,
{:render_to_static_markup, component, props},
render_timeout
) do
{:ok, html} = reply ->
Cache.put(component, props, :render_to_static_markup, html)
reply
reply ->
reply
end
html ->
{:ok, html}
end
{:reply, reply, state}
end
def handle_call(:stop_runtime, _from, %{runtime_process: runtime_process} = state) do
ok = GenServer.cast(runtime_process, :shutdown)
{:reply, ok, state}
end
end