From 94173e81896360aac9f2f5f34247a93da334733c Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 15 Nov 2023 17:13:40 -0500 Subject: [PATCH 1/3] Compile CSS using the post-processed templates from pages Close #194 --- lib/beacon/lifecycle/template.ex | 2 +- lib/beacon/loader.ex | 21 ++++++++------ lib/beacon/loader/page_module_loader.ex | 2 +- lib/beacon/media_library.ex | 4 ++- lib/beacon/router.ex | 24 +++++++++++++--- lib/beacon/tailwind_compiler.ex | 28 +++++++++++------- lib/beacon/template.ex | 9 +++++- test/beacon/tailwind_compiler_test.exs | 38 +++++++++++++++++++++++-- 8 files changed, 99 insertions(+), 29 deletions(-) diff --git a/lib/beacon/lifecycle/template.ex b/lib/beacon/lifecycle/template.ex index f063994b..81c77aa3 100644 --- a/lib/beacon/lifecycle/template.ex +++ b/lib/beacon/lifecycle/template.ex @@ -101,7 +101,7 @@ defmodule Beacon.Lifecycle.Template do @spec render_template(Beacon.Content.Page.t(), module(), map(), Macro.Env.t()) :: Beacon.Template.t() def render_template(page, page_module, assigns, env) do template = - case page_module.render(assigns) do + case Beacon.Template.render(page_module, assigns) do %Phoenix.LiveView.Rendered{} = rendered -> rendered :not_loaded -> Beacon.Loader.load_page_template(page, page_module, assigns) end diff --git a/lib/beacon/loader.ex b/lib/beacon/loader.ex index 455e517b..7d21785d 100644 --- a/lib/beacon/loader.ex +++ b/lib/beacon/loader.ex @@ -126,13 +126,13 @@ defmodule Beacon.Loader do defp load_site_from_db(site) do with :ok <- Beacon.RuntimeJS.load!(), - :ok <- load_runtime_css(site), - :ok <- load_stylesheets(site), :ok <- load_components(site), :ok <- load_snippet_helpers(site), :ok <- load_layouts(site), :ok <- load_pages(site), - :ok <- load_error_pages(site) do + :ok <- load_error_pages(site), + :ok <- load_stylesheets(site), + :ok <- load_runtime_css(site) do :ok else _ -> raise Beacon.LoaderError, message: "failed to load resources for site #{site}" @@ -199,12 +199,15 @@ defmodule Beacon.Loader do # too slow to run the css compiler on every test if Code.ensure_loaded?(Mix.Project) and Mix.env() == :test do - defp load_runtime_css(_site), do: :ok + @doc false + def load_runtime_css(_site), do: :ok else - defp load_runtime_css(site), do: Beacon.RuntimeCSS.load!(site) + @doc false + def load_runtime_css(site), do: Beacon.RuntimeCSS.load!(site) end - defp load_stylesheets(site) do + @doc false + def load_stylesheets(site) do StylesheetModuleLoader.load_stylesheets(site, Content.list_stylesheets(site)) :ok end @@ -222,7 +225,8 @@ defmodule Beacon.Loader do :ok end - defp load_layouts(site) do + @doc false + def load_layouts(site) do site |> Content.list_published_layouts() |> Enum.map(fn layout -> @@ -236,7 +240,8 @@ defmodule Beacon.Loader do :ok end - defp load_pages(site) do + @doc false + def load_pages(site) do site |> Content.list_published_pages() |> Enum.map(fn page -> diff --git a/lib/beacon/loader/page_module_loader.ex b/lib/beacon/loader/page_module_loader.ex index ffa75262..5dd240f8 100644 --- a/lib/beacon/loader/page_module_loader.ex +++ b/lib/beacon/loader/page_module_loader.ex @@ -41,7 +41,7 @@ defmodule Beacon.Loader.PageModuleLoader do with %Content.Page{} = page <- Beacon.Content.get_published_page(page.site, page.id), {:ok, ^page_module, _ast} <- do_load_page!(page, :request), - %Phoenix.LiveView.Rendered{} = rendered <- page_module.render(assigns) do + %Phoenix.LiveView.Rendered{} = rendered <- Beacon.Template.render(assigns) do rendered else _ -> diff --git a/lib/beacon/media_library.ex b/lib/beacon/media_library.ex index 50dfdf95..4c82624e 100644 --- a/lib/beacon/media_library.ex +++ b/lib/beacon/media_library.ex @@ -103,7 +103,7 @@ defmodule Beacon.MediaLibrary do |> Enum.map(&get_url_for(&1, asset)) end - def srcset_for_image(asset, sources) do + def srcset_for_image(%Asset{} = asset, sources) do asset = Repo.preload(asset, :assets) asset.assets @@ -111,6 +111,8 @@ defmodule Beacon.MediaLibrary do |> build_srcset() end + def srcset_for_image(_asset, _sources), do: [] + defp filter_sources(assets, sources) do Enum.filter( assets, diff --git a/lib/beacon/router.ex b/lib/beacon/router.ex index 4588070c..4ee73c3e 100644 --- a/lib/beacon/router.ex +++ b/lib/beacon/router.ex @@ -207,10 +207,26 @@ defmodule Beacon.Router do @doc false def dump_pages do - case :ets.match(@ets_table, :"$1") do - [] -> [] - [pages] -> pages - end + @ets_table |> :ets.match(:"$1") |> List.flatten() + end + + @doc false + def dump_pages(site) do + match = {{site, :_}, :_} + guards = [] + body = [:"$_"] + + @ets_table + |> :ets.select([{match, guards, body}]) + |> List.flatten() + end + + def dump_page_modules(site, fun \\ &Function.identity/1) do + site + |> dump_pages() + |> Enum.map(fn {{^site, _path} = key, {_page_id, _layout_id, _format, page_module, _component_module}} -> + fun.(Tuple.append(key, page_module)) + end) end @doc false diff --git a/lib/beacon/tailwind_compiler.ex b/lib/beacon/tailwind_compiler.ex index 833e40cb..c404b46b 100644 --- a/lib/beacon/tailwind_compiler.ex +++ b/lib/beacon/tailwind_compiler.ex @@ -144,13 +144,6 @@ defmodule Beacon.TailwindCompiler do defp generate_template_files!(tmp_dir, site) do [ - Task.async(fn -> - Enum.map(Content.list_layouts(site, per_page: :infinity), fn layout -> - layout_path = Path.join(tmp_dir, "#{site}_layout_#{remove_special_chars(layout.title)}.template") - File.write!(layout_path, layout.template) - layout_path - end) - end), Task.async(fn -> Enum.map(Beacon.Content.list_components(site, per_page: :infinity), fn component -> component_path = Path.join(tmp_dir, "#{site}_component_#{remove_special_chars(component.name)}.template") @@ -159,9 +152,24 @@ defmodule Beacon.TailwindCompiler do end) end), Task.async(fn -> - Enum.map(Content.list_pages(site, per_page: :infinity), fn page -> - page_path = Path.join(tmp_dir, "#{site}_page_#{remove_special_chars(page.path)}.template") - File.write!(page_path, page.template) + Enum.map(Content.list_layouts(site, per_page: :infinity), fn layout -> + layout_path = Path.join(tmp_dir, "#{site}_layout_#{remove_special_chars(layout.title)}.template") + File.write!(layout_path, layout.template) + layout_path + end) + end), + Task.async(fn -> + # parse from laoded pages (ETS) so it can fetch callback transformations + # thay may include additoinal stylesheet classes as the markdown parser does + Beacon.Router.dump_page_modules(site, fn {_site, path, page_module} -> + template = + page_module + |> Beacon.Template.render() + |> Map.get(:static) + |> List.to_string() + + page_path = Path.join(tmp_dir, "#{site}_page_#{remove_special_chars(path)}.template") + File.write!(page_path, template) page_path end) end), diff --git a/lib/beacon/template.ex b/lib/beacon/template.ex index 8559fa4a..59dc88f1 100644 --- a/lib/beacon/template.ex +++ b/lib/beacon/template.ex @@ -49,7 +49,7 @@ defmodule Beacon.Template do # it is NOT supposed to be used to render templates def __render__(site, path_list) when is_list(path_list) do case Beacon.Router.lookup_path(site, path_list) do - {{site, path}, {page_id, _layout_id, format, page_module, component_module}} -> + {{^site, path}, {page_id, _layout_id, format, page_module, component_module}} -> assigns = %{__changed__: %{}, __live_path__: [], __beacon_page_module__: page_module, __beacon_component_module__: component_module} page = %Beacon.Content.Page{id: page_id, site: site, path: path, format: format} Beacon.Lifecycle.Template.render_template(page, page_module, assigns, BeaconWeb.PageLive.make_env()) @@ -59,6 +59,13 @@ defmodule Beacon.Template do end end + @doc false + def render(page_module, assigns \\ %{}) when is_atom(page_module) and is_map(assigns) do + %{__changed__: %{}, __live_path__: [], beacon_path_params: %{}, beacon_live_data: %{}} + |> Map.merge(assigns) + |> page_module.render() + end + @doc false def choose_template([primary]), do: primary def choose_template([primary | variants]), do: choose_template(variants, Enum.random(1..100), primary) diff --git a/test/beacon/tailwind_compiler_test.exs b/test/beacon/tailwind_compiler_test.exs index 3b658e45..5c723506 100644 --- a/test/beacon/tailwind_compiler_test.exs +++ b/test/beacon/tailwind_compiler_test.exs @@ -1,10 +1,17 @@ defmodule Beacon.TailwindCompilerTest do - use Beacon.DataCase, async: true + use Beacon.DataCase, async: false import ExUnit.CaptureIO import Beacon.Fixtures alias Beacon.TailwindCompiler + @site :my_site + + setup_all do + start_supervised!({Beacon.Loader, Beacon.Config.fetch!(:my_site)}) + :ok + end + defp create_page(_) do stylesheet_fixture() @@ -17,15 +24,16 @@ defmodule Beacon.TailwindCompilerTest do ) layout = - layout_fixture( + published_layout_fixture( template: """
Page header
<%= @inner_content %> """ ) - page_fixture( + published_page_fixture( layout_id: layout.id, + path: "/a", template: """

Some Values:

@@ -36,6 +44,22 @@ defmodule Beacon.TailwindCompilerTest do """ ) + page_fixture( + layout_id: layout.id, + path: "/b", + template: """ +
+

Some Values:

+
+ """ + ) + + Beacon.Loader.load_stylesheets(@site) + Beacon.Loader.load_components(@site) + Beacon.Loader.load_layouts(@site) + Beacon.Loader.load_pages(@site) + Beacon.Loader.load_runtime_css(@site) + :ok end @@ -56,5 +80,13 @@ defmodule Beacon.TailwindCompilerTest do assert output =~ "text-gray-200" end) end + + test "do not include classes from unpublished pages" do + capture_io(fn -> + assert {:ok, output} = TailwindCompiler.compile(:my_site) + + refute output =~ "text-gray-300" + end) + end end end From 037717b26ee7c798a9510c1d345d1f923e769744 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Wed, 15 Nov 2023 17:15:41 -0500 Subject: [PATCH 2/3] make sure the template is loaded --- lib/beacon/tailwind_compiler.ex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/beacon/tailwind_compiler.ex b/lib/beacon/tailwind_compiler.ex index c404b46b..dc810464 100644 --- a/lib/beacon/tailwind_compiler.ex +++ b/lib/beacon/tailwind_compiler.ex @@ -165,7 +165,7 @@ defmodule Beacon.TailwindCompiler do template = page_module |> Beacon.Template.render() - |> Map.get(:static) + |> fetch_static() |> List.to_string() page_path = Path.join(tmp_dir, "#{site}_page_#{remove_special_chars(path)}.template") @@ -185,6 +185,9 @@ defmodule Beacon.TailwindCompiler do |> List.flatten() end + defp fetch_static(%{static: static}), do: static + defp fetch_static(_), do: [] + # import app css into input css used by tailwind-cli to load tailwind functions and directives defp generate_input_css_file!(tmp_dir, site) do beacon_tailwind_css_path = Path.join([Application.app_dir(:beacon), "priv", "beacon_tailwind.css"]) From d8d2e3b974d7e18e6710f60e9eb0e7cb1008b23a Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Tue, 21 Nov 2023 11:21:08 -0500 Subject: [PATCH 3/3] load css after loading pages so the page module is updated on ets --- lib/beacon/loader.ex | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/lib/beacon/loader.ex b/lib/beacon/loader.ex index 7d21785d..4764515c 100644 --- a/lib/beacon/loader.ex +++ b/lib/beacon/loader.ex @@ -365,8 +365,9 @@ defmodule Beacon.Loader do @doc false def handle_call({:load_page, page}, _from, config) do + result = do_load_page!(page) :ok = load_runtime_css(page.site) - {:reply, do_load_page(page), config} + {:reply, result, config} end def handle_call({:load_page_template, page, page_module, assigns}, _from, config) do @@ -384,14 +385,14 @@ defmodule Beacon.Loader do def handle_info({:layout_published, %{site: site, id: id}}, state) do layout = Content.get_published_layout(site, id) - with :ok <- load_runtime_css(site), - # TODO: load only used components, depends on https://github.com/BeaconCMS/beacon/issues/84 - :ok <- load_components(site), + # TODO: load only used components, depends on https://github.com/BeaconCMS/beacon/issues/84 + with :ok <- load_components(site), # TODO: load only used snippet helpers :ok <- load_snippet_helpers(site), - :ok <- load_stylesheets(site), {:ok, _module, _ast} <- Beacon.Loader.LayoutModuleLoader.load_layout!(layout), - :ok <- maybe_reload_error_pages(layout) do + :ok <- maybe_reload_error_pages(layout), + :ok <- load_runtime_css(site), + :ok <- load_stylesheets(site) do :ok else _ -> raise Beacon.LoaderError, message: "failed to load resources for layout #{layout.title} of site #{layout.site}" @@ -402,25 +403,25 @@ defmodule Beacon.Loader do @doc false def handle_info({:page_published, %{site: site, id: id}}, state) do - :ok = load_runtime_css(site) - site |> Content.get_published_page(id) - |> do_load_page() + |> do_load_page!() + + :ok = load_runtime_css(site) {:noreply, state} end @doc false def handle_info({:pages_published, site, pages}, state) do - :ok = load_runtime_css(site) - for page <- pages do site |> Content.get_published_page(page.id) - |> do_load_page() + |> do_load_page!() end + :ok = load_runtime_css(site) + {:noreply, state} end @@ -454,9 +455,9 @@ defmodule Beacon.Loader do {:noreply, state} end - defp do_load_page(page) when is_nil(page), do: nil + defp do_load_page!(page) when is_nil(page), do: nil - defp do_load_page(page) do + defp do_load_page!(page) do layout = Content.get_published_layout(page.site, page.layout_id) # TODO: load only used components, depends on https://github.com/BeaconCMS/beacon/issues/84