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/mix/build/bun.ex
defmodule Mix.Tasks.Phx.React.Bun.Bundle do
@moduledoc """
Create server.js bundle for `bun` runtime,
bundle all components and render server in one file for otp release.
## Usage
```shell
mix phx.react.bun.bundle --component-base=assets/component --output=priv/react/server.js
```
"""
require Logger
use Mix.Task
@shortdoc "Bundle components into server.js"
def run(args) do
{opts, _argv} =
OptionParser.parse!(args,
strict: [component_base: :string, output: :string, cd: :string, development: :boolean]
)
component_base = Keyword.get(opts, :component_base)
base_dir = Path.absname(component_base, File.cwd!()) |> Path.expand()
components =
if File.dir?(base_dir) do
find_files(base_dir)
else
throw("component_base dir is not exists: #{base_dir}")
end
output = Keyword.get(opts, :output)
Logger.info("Bundle component in directory [#{component_base}] into #{output}")
cd = Keyword.get(opts, :cd, File.cwd!())
files =
components
|> Enum.map(fn abs_path ->
filename = Path.relative_to(abs_path, base_dir)
ext = Path.extname(filename)
basename = Path.basename(filename, ext)
{basename, abs_path}
end)
quoted = EEx.compile_file("#{__DIR__}/server_bun.eex")
{result, _bindings} = Code.eval_quoted(quoted, files: files, base_dir: base_dir)
tmp_file = "#{cd}/server.js"
File.write!(tmp_file, result)
outdir = Path.dirname(output)
if File.exists?(output) do
File.rm!(output)
end
# Build with optimizations for production (unless development mode)
is_dev = Keyword.get(opts, :development, false)
build_args = [
"build",
"--target=bun",
"--outdir=#{outdir}",
tmp_file
]
# Add production optimizations unless in development mode
build_args =
if is_dev do
build_args
else
build_args ++
[
"--minify",
"--sourcemap=external",
"--define=process.env.NODE_ENV=\"production\""
]
end
{out, code} = System.cmd("bun", build_args, cd: cd)
Logger.info(~s[cd #{cd}; bun build --target=bun --outdir=#{outdir} #{tmp_file}])
Logger.info("out #{code}: #{out}")
if code != 0 do
throw("bun build failed(#{code})")
end
if Path.join(outdir, "server.js") != output do
File.rename(Path.join(outdir, "server.js"), output)
end
File.rm!(tmp_file)
rescue
error ->
Logger.error("Build error: #{inspect(error)}")
catch
error ->
Logger.error("Build caught error: #{inspect(error)}")
end
def find_files(dir) do
find_files(dir, [])
end
defp find_files(dir, acc) do
case File.ls(dir) do
{:ok, entries} ->
Enum.reduce(entries, acc, &process_entry(dir, &1, &2))
# Ignore errors (e.g., permission issues)
{:error, _} ->
acc
end
end
defp process_entry(dir, entry, acc) do
path = Path.join(dir, entry)
cond do
# Recurse into subdirectories
File.dir?(path) -> find_files(path, acc)
# Collect files
File.regular?(path) -> [path | acc]
true -> acc
end
end
end