Skip to content

Commit e1224c9

Browse files
gsmlgGSMLG-BOT
andauthored
feat: Run react render server in a new mode. (#6)
Run react render server in a new mode. More component can now be rendered in `SSR`. --------- Co-authored-by: Jonathan Gao <[email protected]>
1 parent 676f822 commit e1224c9

File tree

25 files changed

+151519
-7839
lines changed

25 files changed

+151519
-7839
lines changed

lib/phoenix/react.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ defmodule Phoenix.React do
153153
def init(_init_arg) do
154154
children = [
155155
{Phoenix.React.Cache, []},
156+
{Phoenix.React.Runtime, []},
156157
{Phoenix.React.Server, []}
157158
]
158159

lib/phoenix/react/runtime.ex

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,32 @@ defmodule Phoenix.React.Runtime do
44
55
"""
66

7-
defstruct [:component_base, :port, render_timeout: 300_000]
7+
use DynamicSupervisor
8+
9+
def start_link(init_arg) do
10+
DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
11+
end
12+
13+
def init(_init_arg) do
14+
DynamicSupervisor.init(strategy: :one_for_one)
15+
end
16+
17+
def start_runtime(runtime, args) do
18+
spec = {runtime, args}
19+
DynamicSupervisor.start_child(__MODULE__, spec)
20+
end
21+
22+
def start_file_watcher(args) do
23+
spec = {Phoenix.React.Runtime.FileWatcher, args}
24+
DynamicSupervisor.start_child(__MODULE__, spec)
25+
end
26+
27+
defstruct [:component_base, :port, :server_js, render_timeout: 300_000]
828

929
@type t :: %__MODULE__{
1030
render_timeout: integer(),
1131
component_base: path(),
32+
server_js: path(),
1233
port: port()
1334
}
1435

@@ -20,6 +41,8 @@ defmodule Phoenix.React.Runtime do
2041

2142
@callback start([{:component_base, path()}, {:render_timeout, integer()}]) :: port()
2243

44+
@callback start_file_watcher(path()) :: :ok
45+
2346
@callback config() :: list()
2447

2548
@callback render_to_string(component(), map(), GenServer.from(), t()) ::

lib/phoenix/react/runtime/bun.ex

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,36 @@ defmodule Phoenix.React.Runtime.Bun do
2121
use Phoenix.React.Runtime
2222

2323
def start_link(init_arg) do
24+
IO.inspect(init_arg)
2425
GenServer.start_link(__MODULE__, init_arg, name: __MODULE__)
2526
end
2627

2728
@impl true
2829
def init(component_base: component_base, render_timeout: render_timeout) do
29-
{:ok, %Runtime{component_base: component_base, render_timeout: render_timeout},
30-
{:continue, :start_port}}
30+
{:ok,
31+
%Runtime{
32+
component_base: component_base,
33+
render_timeout: render_timeout,
34+
server_js: config()[:server_js]
35+
}, {:continue, :start_port}}
3136
end
3237

3338
@impl true
3439
@spec handle_continue(:start_port, Phoenix.React.Runtime.t()) ::
3540
{:noreply, Phoenix.React.Runtime.t()}
3641
def handle_continue(:start_port, %Runtime{component_base: component_base} = state) do
42+
if config()[:env] == :dev do
43+
start_file_watcher(component_base)
44+
end
45+
3746
port = start(component_base: component_base)
3847

3948
Logger.debug(
4049
"Bun.Server started on port: #{inspect(port)} and OS pid: #{get_port_os_pid(port)}"
4150
)
4251

52+
Phoenix.React.Server.set_runtime_process(self())
53+
4354
{:noreply, %Runtime{state | port: port}}
4455
end
4556

@@ -64,8 +75,12 @@ defmodule Phoenix.React.Runtime.Bun do
6475
def start(component_base: component_base) do
6576
cd = config()[:cd]
6677
cmd = config()[:cmd]
67-
args = ["--port", Integer.to_string(config()[:port]), config()[:server_js]]
68-
bun_env = if(config()[:env] == :dev, do: "development", else: "production")
78+
bun_port = Integer.to_string(config()[:port])
79+
args = ["--port", bun_port, config()[:server_js]]
80+
81+
is_dev = config()[:env] == :dev
82+
83+
bun_env = if(is_dev, do: "development", else: "production")
6984

7085
args =
7186
if config()[:env] == :dev do
@@ -75,6 +90,9 @@ defmodule Phoenix.React.Runtime.Bun do
7590
end
7691

7792
env = [
93+
{~c"no_proxy", ~c"10.*,127.*,192.168.*,172.16.0.0/12,localhost,127.0.0.1,::1"},
94+
{~c"PORT", ~c"#{bun_port}"},
95+
{~c"BUN_PORT", ~c"#{bun_port}"},
7896
{~c"BUN_ENV", ~c"#{bun_env}"},
7997
{~c"COMPONENT_BASE", ~c"#{component_base}"}
8098
]
@@ -96,6 +114,36 @@ defmodule Phoenix.React.Runtime.Bun do
96114
end
97115

98116
@impl true
117+
def start_file_watcher(component_base) do
118+
Logger.debug("Building server.js bundle")
119+
120+
Mix.Task.run("phx.react.bun.bundle", [
121+
"--component-base",
122+
component_base,
123+
"--output",
124+
config()[:server_js]
125+
])
126+
127+
Logger.debug("Starting file watcher")
128+
Runtime.start_file_watcher(ref: self(), path: component_base)
129+
end
130+
131+
@impl true
132+
def handle_info({:component_base_changed, path}, state) do
133+
Logger.debug("component_base changed: #{path}")
134+
135+
Task.async(fn ->
136+
Mix.Task.run("phx.react.bun.bundle", [
137+
"--component-base",
138+
state.component_base,
139+
"--output",
140+
config()[:server_js]
141+
])
142+
end)
143+
144+
{:noreply, state}
145+
end
146+
99147
def handle_info({_port, {:data, msg}}, state) do
100148
Logger.debug(msg)
101149
{:noreply, state}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
defmodule Phoenix.React.Runtime.FileWatcher do
2+
use GenServer
3+
4+
def start_link(args) do
5+
GenServer.start_link(__MODULE__, args, name: __MODULE__)
6+
end
7+
8+
def init(args) do
9+
path = Keyword.fetch!(args, :path)
10+
{:ok, watcher_pid} = FileSystem.start_link(dirs: [path])
11+
FileSystem.subscribe(watcher_pid)
12+
IO.puts("Watching #{path} for changes...")
13+
14+
{:ok,
15+
args
16+
|> Keyword.put(:watcher_pid, watcher_pid)
17+
|> Keyword.put(:update_time, System.os_time(:second))}
18+
end
19+
20+
def handle_info({:file_event, _watcher_pid, {path, [:modified, :closed]}}, state) do
21+
IO.puts("File changed: #{path} - Events: [:modified, :closed]")
22+
send(self(), {:throttle_update, path})
23+
{:noreply, state}
24+
end
25+
26+
def handle_info({:file_event, _watcher_pid, {_path, _events}}, state) do
27+
# ref = Keyword.fetch!(state, :ref)
28+
# IO.puts("File changed: #{path} - Events: #{inspect(events)}")
29+
# send(self(), {:throttle_update, path, events})
30+
{:noreply, state}
31+
end
32+
33+
def handle_info({:throttle_update, path}, state) do
34+
update_time = Keyword.fetch!(state, :update_time)
35+
now = System.os_time(:second)
36+
37+
if now > update_time do
38+
Process.send_after(state[:ref], {:component_base_changed, path}, 3_000)
39+
{:noreply, state |> Keyword.put(:update_time, now + 3)}
40+
else
41+
{:noreply, state}
42+
end
43+
end
44+
end

lib/phoenix/react/server.ex

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@ defmodule Phoenix.React.Server do
88
require Logger
99

1010
alias Phoenix.React.Cache
11+
alias Phoenix.React.Runtime
1112

1213
use GenServer
1314

1415
@type second() :: integer()
1516
@type millisecond() :: integer()
1617

18+
def set_runtime_process(pid) do
19+
GenServer.cast(__MODULE__, {:set_runtime_process, pid})
20+
end
21+
1722
@doc """
1823
Return the configuration of the React Render Server from `Application.get_env(:phoenix_react_server, Phoenix.React)`
1924
"""
@@ -46,14 +51,21 @@ defmodule Phoenix.React.Server do
4651
runtime = cfg[:runtime]
4752
component_base = cfg[:component_base]
4853
render_timeout = cfg[:render_timeout]
54+
args = [component_base: component_base, render_timeout: render_timeout]
4955

50-
{:ok, runtiem_process} =
51-
GenServer.start_link(runtime,
52-
component_base: component_base,
53-
render_timeout: render_timeout
54-
)
56+
Runtime.start_runtime(runtime, args)
5557

56-
{:ok, %{runtiem_process: runtiem_process}}
58+
{:ok,
59+
%{
60+
runtime: runtime,
61+
component_base: component_base,
62+
render_timeout: render_timeout
63+
}}
64+
end
65+
66+
@impl true
67+
def handle_cast({:set_runtime_process, pid}, state) do
68+
{:noreply, Map.put(state, :runtiem_process, pid)}
5769
end
5870

5971
@impl true

mix.exs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ defmodule Phoenix.React.Mixfile do
22
use Mix.Project
33

44
@source_url "https://github.com/gsmlg-dev/phoenix-react.git"
5-
@version "0.3.0"
5+
@version "0.4.0"
66

77
def project do
88
[
@@ -47,7 +47,8 @@ defmodule Phoenix.React.Mixfile do
4747
{:httpoison, "~> 2.0"},
4848
{:phoenix_html, "~> 4.1"},
4949
{:phoenix_live_view, "~> 1.0"},
50-
{:ex_doc, ">= 0.0.0", only: :prod, runtime: false}
50+
{:file_system, "~> 1.0"},
51+
{:ex_doc, ">= 0.0.0", only: :doc, runtime: false}
5152
]
5253
end
5354

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React from "react";
2+
import MDEditor from '@uiw/react-md-editor';
3+
import "@uiw/react-md-editor/markdown-editor.css";
4+
import "@uiw/react-markdown-preview/markdown.css";
5+
6+
export function Component({ data = "", setData }) {
7+
const { content } = data;
8+
9+
return (
10+
<div className="container">
11+
<h1>Live Form</h1>
12+
<form>
13+
<MDEditor
14+
value={content}
15+
onChange={(value) => setData({ content: value })}
16+
/>
17+
<br />
18+
<br />
19+
<MDEditor.Markdown
20+
source={content}
21+
style={{
22+
whiteSpace: 'pre-wrap',
23+
padding: '1rem',
24+
}}
25+
/>
26+
</form>
27+
</div>
28+
);
29+
}

react_demo/assets/css/app.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
@import "./github-markdown.css";
66

7+
@import "@uiw/react-md-editor/markdown-editor.css";
8+
@import "@uiw/react-markdown-preview/markdown.css";
9+
710
@layer components {
811
.table-primary-border {
912
th, td {

react_demo/assets/js/app.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ import "phoenix_html"
33
import {Socket} from "phoenix"
44
import {LiveSocket} from "phoenix_live_view"
55

6+
import { hooks } from './hooks';
7+
68
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
79
let liveSocket = new LiveSocket("/live", Socket, {
810
longPollFallbackMs: 2500,
9-
params: {_csrf_token: csrfToken}
11+
params: {_csrf_token: csrfToken},
12+
hooks,
1013
})
1114

1215
// connect if there are any LiveViews on the page

0 commit comments

Comments
 (0)