@@ -41,7 +41,49 @@ defmodule Beacon.RuntimeRenderer do
4141 def publish_page ( site , page_id , attrs ) when is_atom ( site ) and is_binary ( page_id ) do
4242 template = Map . fetch! ( attrs , :template )
4343 path = Map . get ( attrs , :path , "/" )
44+ format = Map . get ( attrs , :format , :heex )
4445
46+ if format == :beacon do
47+ publish_page_beacon ( site , page_id , template , attrs )
48+ else
49+ publish_page_heex ( site , page_id , template , path , attrs )
50+ end
51+ end
52+
53+ defp publish_page_beacon ( site , page_id , template , attrs ) do
54+ path = Map . get ( attrs , :path , "/" )
55+
56+ # 1. Parse Beacon template syntax to platform-agnostic AST
57+ page_ast = Beacon.Template.Parser . parse ( template )
58+
59+ # 2. Expand components into the AST
60+ component_registry = build_component_registry ( site )
61+ expanded_ast = Beacon.Template.ComponentExpander . expand ( page_ast , component_registry )
62+
63+ # 3. Store the AST as JSON in ETS
64+ :ets . insert ( @ table , { { site , page_id , :ast } , expanded_ast } )
65+
66+ # 4. Store manifest and route
67+ store_page_metadata ( site , page_id , attrs )
68+
69+ # 5. Store event handlers
70+ store_page_handlers ( site , page_id , attrs )
71+
72+ # 6. CSS candidates from the template source
73+ candidates = Beacon.CSS.CandidateExtractor . extract ( template )
74+ :ets . insert ( @ table , { { site , page_id , :css_candidates } , candidates } )
75+ update_site_css_candidates ( site , candidates )
76+
77+ # 7. Register page dependencies
78+ Beacon.PageRenderCache . register_page_deps ( site , page_id , % {
79+ layout_id: Map . get ( attrs , :layout_id ) ,
80+ components: MapSet . new ( )
81+ } )
82+
83+ :ok
84+ end
85+
86+ defp publish_page_heex ( site , page_id , template , path , attrs ) do
4587 # 1. Compile HEEx to AST via standard Phoenix pipeline
4688 env = Beacon.Web.PageLive . make_env ( site )
4789 { :ok , ast } = Beacon.Template.HEEx . compile ( site , path , template )
@@ -50,7 +92,28 @@ defmodule Beacon.RuntimeRenderer do
5092 ir = extract_ir ( ast , env )
5193 :ets . insert ( @ table , { { site , page_id , :ir } , :erlang . term_to_binary ( ir ) } )
5294
53- # 3. Store the page manifest — everything needed to mount and render
95+ # 3. Store metadata, handlers, CSS
96+ store_page_metadata ( site , page_id , attrs )
97+ store_page_handlers ( site , page_id , attrs )
98+
99+ candidates = Beacon.CSS.CandidateExtractor . extract ( template )
100+ :ets . insert ( @ table , { { site , page_id , :css_candidates } , candidates } )
101+ update_site_css_candidates ( site , candidates )
102+
103+ # 4. Register page dependencies for cascade invalidation
104+ component_names = Beacon.PageRenderCache . extract_component_names ( ir )
105+
106+ Beacon.PageRenderCache . register_page_deps ( site , page_id , % {
107+ layout_id: Map . get ( attrs , :layout_id ) ,
108+ components: component_names
109+ } )
110+
111+ :ok
112+ end
113+
114+ defp store_page_metadata ( site , page_id , attrs ) do
115+ path = Map . get ( attrs , :path , "/" )
116+
54117 manifest = % {
55118 id: page_id ,
56119 site: site ,
@@ -65,15 +128,13 @@ defmodule Beacon.RuntimeRenderer do
65128 }
66129
67130 :ets . insert ( @ table , { { site , page_id , :manifest } , manifest } )
68-
69- # 4. Register route: path → page_id (for mount-time lookup)
70131 :ets . insert ( @ table , { { site , :route , path } , page_id } )
71132
72- # 5. Store custom page assigns
73133 static_assigns = Map . get ( attrs , :assigns , % { } )
74134 :ets . insert ( @ table , { { site , page_id , :assigns } , static_assigns } )
135+ end
75136
76- # 6. Store event handlers — parse to AST at publish time, not runtime
137+ defp store_page_handlers ( site , page_id , attrs ) do
77138 handlers = Map . get ( attrs , :event_handlers , [ ] )
78139
79140 for % { name: name , code: code } <- handlers do
@@ -84,19 +145,16 @@ defmodule Beacon.RuntimeRenderer do
84145 handler_names = Enum . map ( handlers , & & 1 . name )
85146 :ets . insert ( @ table , { { site , page_id , :handler_index } , handler_names } )
86147
87- # 8. Store page helpers (dynamic_helper calls)
88148 helpers = Map . get ( attrs , :helpers , [ ] )
89149
90150 for helper <- helpers do
91151 helper_ast = Code . string_to_quoted! ( helper . code )
92152 args_ast = Code . string_to_quoted! ( helper . args )
93153 :ets . insert ( @ table , { { site , page_id , :helper , helper . name } , :erlang . term_to_binary ( % { code: helper_ast , args: args_ast } ) } )
94154 end
155+ end
95156
96- # 9. Extract CSS candidates and track for conditional recompilation
97- candidates = Beacon.CSS.CandidateExtractor . extract ( template )
98- :ets . insert ( @ table , { { site , page_id , :css_candidates } , candidates } )
99-
157+ defp update_site_css_candidates ( site , candidates ) do
100158 known =
101159 case :ets . lookup ( @ table , { site , :css_candidates } ) do
102160 [ { _ , existing } ] -> existing
@@ -109,16 +167,25 @@ defmodule Beacon.RuntimeRenderer do
109167 updated = MapSet . union ( known , new_classes )
110168 :ets . insert ( @ table , { { site , :css_candidates } , updated } )
111169 end
170+ end
112171
113- # 10. Register page dependencies for cascade invalidation
114- component_names = Beacon.PageRenderCache . extract_component_names ( ir )
115-
116- Beacon.PageRenderCache . register_page_deps ( site , page_id , % {
117- layout_id: manifest . layout_id ,
118- components: component_names
119- } )
172+ defp build_component_registry ( site ) do
173+ # Build a map of component name → AST from stored component templates
174+ case :ets . match ( @ table , { { site , :component , :"$1" } , :"$2" } ) do
175+ matches when is_list ( matches ) ->
176+ Map . new ( matches , fn [ name , binary ] ->
177+ component = :erlang . binary_to_term ( binary )
178+ component_ast =
179+ case component do
180+ % { ast: ast } when is_list ( ast ) -> ast
181+ _ -> [ ]
182+ end
183+ { to_string ( name ) , component_ast }
184+ end )
120185
121- :ok
186+ _ ->
187+ % { }
188+ end
122189 end
123190
124191 # ---------------------------------------------------------------------------
@@ -1058,15 +1125,26 @@ defmodule Beacon.RuntimeRenderer do
10581125 # ---------------------------------------------------------------------------
10591126
10601127 def render_page ( site , page_id , assigns \\ % { } ) when is_atom ( site ) do
1061- case :ets . lookup ( @ table , { site , page_id , :ir } ) do
1062- [ { _ , serialized_ir } ] ->
1063- ir = :erlang . binary_to_term ( serialized_ir )
1128+ # Try AST-based rendering first (new platform-agnostic format)
1129+ case :ets . lookup ( @ table , { site , page_id , :ast } ) do
1130+ [ { _ , ast } ] when is_list ( ast ) ->
10641131 stored_assigns = fetch_assigns ( site , page_id )
10651132 full_assigns = Map . merge ( stored_assigns , assigns ) |> Map . delete ( :__changed__ )
1066- { :ok , render_ir ( ir , full_assigns ) }
1133+ rendered = Beacon.Client.LiveViewCompiler . render ( ast , full_assigns )
1134+ { :ok , rendered }
10671135
1068- [ ] ->
1069- { :error , :not_found }
1136+ _ ->
1137+ # Fall back to legacy IR-based rendering (HEEx format)
1138+ case :ets . lookup ( @ table , { site , page_id , :ir } ) do
1139+ [ { _ , serialized_ir } ] ->
1140+ ir = :erlang . binary_to_term ( serialized_ir )
1141+ stored_assigns = fetch_assigns ( site , page_id )
1142+ full_assigns = Map . merge ( stored_assigns , assigns ) |> Map . delete ( :__changed__ )
1143+ { :ok , render_ir ( ir , full_assigns ) }
1144+
1145+ [ ] ->
1146+ { :error , :not_found }
1147+ end
10701148 end
10711149 end
10721150
0 commit comments