Packages
Phoenix.ReactServer 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
Current section
Files
lib/phoenix/react_server/runtime.ex
defmodule Phoenix.ReactServer.Runtime do
@moduledoc """
Dynamic supervisor for JavaScript runtime processes and file watching.
This module manages the lifecycle of JavaScript runtime processes (Bun/Deno)
and coordinates file watching for hot reloading in development mode.
## Features
- **Runtime Management**: Supervises JavaScript runtime processes
- **Hot Reloading**: Automatic component rebuilding in development
- **File Watching**: Monitors component directory changes with throttling
- **Graceful Restart**: Handles runtime failures and restarts
- **Development Mode**: Enhanced developer experience with live reloading
## Runtime Lifecycle
### Development Mode
1. Starts file watcher for component directory
2. Monitors file changes with 3-second throttle
3. Triggers automatic runtime restart on component changes
4. Provides hot reloading for seamless development
### Production Mode
1. Starts optimized runtime processes
2. No file watching (minimal overhead)
3. Uses pre-bundled components for performance
## Supported Runtimes
- **Bun** (`Phoenix.ReactServer.Runtime.Bun`): Fast JavaScript runtime
- **Deno** (`Phoenix.ReactServer.Runtime.Deno`): Secure runtime with npm support
## Configuration
Runtime behavior is controlled via application configuration:
```elixir
config :phoenix_react_ng, Phoenix.ReactServer.Runtime.Bun,
env: :dev, # :dev or :prod
port: 5225,
component_base: "/path/to/components"
```
## Performance Considerations
- Development mode incurs file watching overhead
- Throttling prevents excessive rebuilds during rapid changes
- Runtime processes are isolated for stability
- Automatic restart ensures reliability
"""
use DynamicSupervisor
def start_link(init_arg) do
DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
end
def init(_init_arg) do
DynamicSupervisor.init(strategy: :one_for_one)
end
def start_runtime(runtime, args) do
spec = {runtime, args}
DynamicSupervisor.start_child(__MODULE__, spec)
end
def start_file_watcher(args) do
spec = {Phoenix.ReactServer.Runtime.FileWatcher, args}
DynamicSupervisor.start_child(__MODULE__, spec)
end
defstruct [:component_base, :runtime_port, :server_js, :cd, render_timeout: 300_000]
@typedoc "Runtime state structure"
@type t :: %__MODULE__{
render_timeout: timeout(),
component_base: Path.t(),
server_js: Path.t(),
cd: Path.t(),
runtime_port: port()
}
@typedoc "File system path"
@type path :: Path.t()
@typedoc "Rendering method"
@type method :: :render_to_readable_stream | :render_to_string | :render_to_static_markup
@typedoc "Component name"
@type component :: String.t()
@typedoc "Rendered HTML output"
@type html :: String.t()
@typedoc "Runtime start arguments"
@type start_args :: [
{:component_base, path()}
| {:render_timeout, timeout()}
]
@typedoc "Runtime configuration"
@type runtime_config :: keyword()
@typedoc "Render response"
@type render_response :: {:ok, html()} | {:error, term()}
@doc """
Starts the runtime with given arguments.
Returns a port that can be used to communicate with the runtime process.
"""
@callback start(start_args()) :: port()
@doc """
Starts a file watcher for the component directory.
"""
@callback start_file_watcher(path()) :: {:ok, pid()} | {:error, term()}
@doc """
Returns runtime configuration.
"""
@callback config() :: runtime_config()
@doc """
Renders a component using the specified method.
"""
@callback get_rendered_component(method(), component(), map(), t()) :: render_response()
defmacro __using__(_) do
quote do
@behaviour Phoenix.ReactServer.Runtime
alias Phoenix.ReactServer.Runtime
use GenServer
@impl true
def handle_call({method, component, props}, _from, state)
when method in [:render_to_readable_stream, :render_to_string, :render_to_static_markup] do
reply = apply(__MODULE__, :get_rendered_component, [method, component, props, state])
{:reply, reply, state}
end
end
end
end