Runtime compiler for Phoenix Layout modules with dynamic content compilation.
This module provides comprehensive functionality to dynamically compile Phoenix Layout modules at runtime from configuration data. It generates complete layout modules with proper template compilation, metadata management, helper functions, and resource handling for CMS layouts.
Features
- Dynamic layout module generation from configuration parameters
- HEEx template compilation with Phoenix LiveView integration
- Layout metadata and information functions for runtime introspection
- Helper function integration for custom layout logic
- Resource management including CSS, JavaScript, and meta tags
- SEO optimization with customizable meta tag support
- Multi-site layout support with site-specific configurations
- Access control integration with permission management
Layout Module Structure
Generated layout modules include:
__layout_information__/0- Returns complete layout metadata__layout_information__/1- Returns specific metadata by key or pathrender/1- Renders layout with assignsrender/2- Renders layout with assigns and inner content- Helper functions (if defined in configuration)
- Proper Phoenix LiveView and MishkaCMS integration
Basic Usage
The primary entry point is build_module/1 which takes layout configuration
and returns a complete AST ready for compilation:
params = %{
title: "Main Layout",
layout_module: MyApp.Layouts.Main,
template: ~S'''
<!DOCTYPE html>
<html>
<head>
<title><%= @title %></title>
<%= for meta <- @seo.meta_tags do %>
<meta name={meta.name} content={meta.content}>
<% end %>
</head>
<body>
<header>Site Header</header>
<main><%= @inner_content %></main>
<footer>Site Footer</footer>
</body>
</html>
''',
site: "main_site",
site_id: 1
}
ast = LayoutCompiler.build_module(params)
MishkaCmsCore.Runtime.Compilers.ModuleCompiler.compile_module(ast)SEO and Resource Management
Layouts provide built-in SEO and resource management:
# SEO configuration
seo_tags: [
%{name: "description", content: "Page description"},
%{name: "keywords", content: "keyword1, keyword2"},
%{property: "og:title", content: "Social Media Title"},
%{property: "og:image", content: "https://example.com/image.png"}
]
# Resource links
resource_links: [
%{rel: "stylesheet", href: "/assets/theme.css", type: "text/css"},
%{rel: "preconnect", href: "https://fonts.googleapis.com"},
%{rel: "dns-prefetch", href: "//cdn.example.com"}
]Helper Functions in Layouts
Helper functions provide custom logic within layouts:
helpers: [
%{
name: "render_breadcrumbs",
args: "breadcrumbs",
code: ~S'''
breadcrumbs
|> Enum.with_index()
|> Enum.map(fn {crumb, index} ->
if index == length(breadcrumbs) - 1 do
~H"<span class='current'><%= crumb.title %></span>"
else
~H"<a href={crumb.path}><%= crumb.title %></a>"
end
end)
|> Enum.intersperse(~H" > ")
'''
}
]Error Handling
The compiler handles various error conditions gracefully:
- Missing template - Uses empty template with warning
- Invalid helper code - Excludes problematic helpers with logging
- Missing required fields - Uses sensible defaults where possible
- Template compilation errors - Provides detailed error information
Performance Considerations
- Template compilation is performed once during module compilation
- Helper functions are compiled as regular module functions
- Metadata access uses compiled functions for optimal performance
- Resource links are pre-processed and escaped during compilation
Security Features
- Template sanitization through Phoenix's HEEx engine
- Access control integration with permission checks
- XSS prevention through proper escaping
- Safe helper execution with proper error boundaries
See individual function documentation for detailed implementation examples.
Summary
Functions
Builds helper functions for custom layout logic.
Builds layout information and metadata functions.
Builds a complete layout module AST from configuration parameters.
Builds render functions for layout template rendering.
Creates the final module AST with all components assembled.
Creates compilation environment for template processing.
Functions
Builds helper functions for custom layout logic.
Processes helper function definitions from the layout configuration and compiles them into the layout module. Helper functions provide custom logic that can be called from within layout templates.
Parameters
{params, functions_ast}- Tuple containing:params- Layout configuration including:helperslistfunctions_ast- Existing function ASTs from previous pipeline stages
Returns
Returns updated tuple {params, functions_ast} with helper functions added.
Helper Function Format
Each helper function should be defined as a map with:
:name- Function name as string (required):args- Function arguments as string (required):code- Function body as Elixir code string (required)
Examples
helpers: [
%{
name: "format_date",
args: "date",
code: ~S'''
case date do
%Date{} -> Date.to_string(date)
%DateTime{} -> DateTime.to_date(date) |> Date.to_string()
_ -> "Invalid date"
end
'''
},
%{
name: "render_navigation",
args: "items, current_path",
code: ~S'''
for item <- items || [] do
active_class = if item.path == current_path, do: "active", else: ""
~H"""
<a href={item.path} class={"nav-link " <> active_class}>
<%= item.title %>
</a>
"""
end
'''
},
%{
name: "calculate_reading_time",
args: "content",
code: ~S'''
word_count = String.split(content || "") |> length()
reading_time = max(1, div(word_count, 200))
"#{reading_time} min read"
'''
}
]Template Usage
Helper functions can be called directly from layout templates:
template = ~S'''
<!DOCTYPE html>
<html>
<head>
<title><%= @title %></title>
</head>
<body>
<nav>
<%= render_navigation(@nav_items, @current_path) %>
</nav>
<main>
<time><%= format_date(@published_at) %></time>
<p>Reading time: <%= calculate_reading_time(@content) %></p>
<%= @inner_content %>
</main>
</body>
</html>
'''Function Compilation
Helper functions are compiled as regular module functions with:
- Full Elixir syntax support - Use any valid Elixir code
- Access to imports - Phoenix.Component, HEEx, etc.
- Pattern matching - Multiple function heads supported
- Guard clauses - When conditions supported
- Private scope - Helper functions are public within the layout module
Advanced Helper Examples
helpers: [
# Multi-clause function with pattern matching
%{
name: "format_currency",
args: "amount, currency \\ "USD"",
code: ~S'''
case currency do
"USD" -> "$#{:erlang.float_to_binary(amount / 100, decimals: 2)}"
"EUR" -> "€#{:erlang.float_to_binary(amount / 100, decimals: 2)}"
_ -> "#{amount}"
end
'''
},
# Helper with HEEx components
%{
name: "render_breadcrumbs",
args: "crumbs",
code: ~S'''
crumbs
|> Enum.with_index()
|> Enum.map(fn {crumb, index} ->
if index == length(crumbs) - 1 do
~H"<span class='breadcrumb-current'><%= crumb.title %></span>"
else
~H"<a href={crumb.url} class='breadcrumb-link'><%= crumb.title %></a>"
end
end)
|> Enum.intersperse(~H" > ")
'''
}
]Error Handling
- Syntax errors in helper code will prevent module compilation
- Invalid arguments will cause compilation failures with detailed messages
- Missing dependencies in helper code will be reported during compilation
- Runtime errors in helpers will be caught and logged during template rendering
Performance Considerations
- Helper functions are compiled once during module compilation
- No runtime overhead for function calls within templates
- Complex helpers should be optimized for template rendering performance
- Consider caching expensive computations within helper implementations
This function is called as the third stage in the compilation pipeline.
Builds layout information and metadata functions.
Creates AST for the __layout_information__ functions that provide comprehensive
access to layout metadata, private information, ACL permissions, and extra data.
These functions are essential for runtime introspection and layout management.
Parameters
params- Atomized layout configuration map containing all layout data
Returns
Returns a tuple {params, functions_ast} where:
params- Original parameters (passed through for pipeline)functions_ast- List containing the layout information function ASTs
Generated Functions
The generated module will contain these information functions:
__layout_information__/0
Returns complete layout metadata as a structured map with sections:
:layout- Core layout data (title, path, content, resources, SEO):private- Private metadata (IDs, module info, site data, whether the layout reads the signed-in user — seeMishkaCmsCore.Runtime.Compilers.UserClosure):acl- Access control permissions:extra- Additional custom metadata
__layout_information__/1 (atom key)
Returns specific top-level section by atom key.
__layout_information__/1 (list of keys)
Returns nested data using a list of keys for deep access.
__layout_information__/1 (dot-notation string)
Returns nested data using dot-notation string paths like "private.site_id".
Layout Information Structure
%{
layout: %{
title: "Layout Title",
path: "/layouts/example",
timestamp: ~N[2024-01-15 10:30:00],
content: "layout content",
resource_links: [%{rel: "stylesheet", href: "/css/main.css"}],
seo: %{meta_tags: [%{name: "description", content: "..."}]}
},
private: %{
id: 123,
layout_module: MyApp.Layouts.Example,
priority: 0,
site: "main_site",
site_id: 1,
host: "example.com",
format: :heex,
reads_user: false
},
acl: %{permissions: ["layout_view", "layout_edit"]},
extra: %{theme: "modern", version: "1.0"}
}Examples
# After compilation, access layout information:
info = MyLayout.__layout_information__()
layout_data = MyLayout.__layout_information__(:layout)
site_id = MyLayout.__layout_information__("private.site_id")
permissions = MyLayout.__layout_information__([:acl, :permissions])Error Handling
The string-based accessor includes error handling for invalid paths,
returning nil when the path cannot be resolved or atoms don't exist.
This function is typically called as the first stage in the compilation pipeline.
Builds a complete layout module AST from configuration parameters.
This is the primary entry point for the layout compilation process. It orchestrates the entire compilation pipeline to transform layout configuration into a complete Phoenix module AST ready for compilation.
Parameters
params- Map containing layout configuration with keys::title- Layout title/name (required):layout_module- Target module name (required):template- HEEx template string (required):path- Layout path/URL (optional):helpers- List of helper function definitions (optional):resource_links- List of CSS/JS resource links (optional):seo_tags- List of SEO meta tags (optional):site,:site_id,:host- Site identification (optional):permissions- Access control permissions (optional):extra- Additional metadata (optional):priority- Layout priority for ordering (optional):format- Template format (:heex, default)
Returns
Returns the complete AST for the layout module that can be compiled using
MishkaCmsCore.Runtime.Compilers.ModuleCompiler.compile_module/1.
Examples
# Basic layout
params = %{
title: "Simple Layout",
layout_module: MyApp.Layouts.Simple,
template: ~S'''
<html>
<body><%= @inner_content %></body>
</html>
'''
}
ast = build_module(params)
# Complex layout with helpers and resources
params = %{
title: "Admin Layout",
layout_module: MyApp.Layouts.Admin,
template: admin_template,
helpers: [
%{name: "admin_nav", args: "user", code: "render_admin_navigation(user)"}
],
resource_links: [
%{rel: "stylesheet", href: "/admin/admin.css"}
],
permissions: ["admin_access"]
}
ast = build_module(params)Compilation Pipeline
The function processes layout data through these stages:
- Information building - Creates metadata and information functions
- Render functions - Compiles template and creates render functions
- Helper functions - Processes and compiles helper functions
- Module creation - Assembles final module AST with imports
Each stage adds specific functionality to the final compiled module.
Builds render functions for layout template rendering.
Compiles the HEEx template and creates render functions that handle different rendering scenarios. The functions automatically merge layout metadata with provided assigns and handle inner content injection.
Parameters
{params, functions_ast}- Tuple containing:params- Layout configuration with template and metadatafunctions_ast- Existing function ASTs from previous pipeline stages
Returns
Returns updated tuple {params, functions_ast} with render functions added.
Generated Functions
render/1 - Basic rendering with assigns
Renders the layout with provided assigns, automatically merging layout metadata into the assigns map.
MyLayout.render(%{
title: "Page Title",
custom_data: "value"
})render/2 - Rendering with inner content
Renders the layout with assigns and injects inner content. The inner content
is available in the template as @inner_content.
MyLayout.render(%{
title: "Page Title"
}, "<div>Page content here</div>")Template Compilation
Templates are compiled using Phoenix's HEEx engine with:
- XSS protection through automatic escaping
- LiveView integration for dynamic updates
- Component support for MishkaCMS runtime components
- Proper error handling with detailed compilation feedback
Layout Data Merging
Both render functions automatically merge layout metadata into assigns:
# Layout data is merged first, then user assigns override
final_assigns = Map.merge(layout_metadata, user_assigns)Available layout data includes:
@title- Layout title@path- Layout path@resource_links- CSS/JS resource links@seo- SEO metadata with meta_tags@content- Layout content- Plus any custom data from the layout configuration
Template Variables
Templates have access to:
- All assigns - User-provided and layout metadata
- Inner content -
@inner_content(render/2 only) - Helper functions - Custom layout helper functions
- MishkaCMS components - Runtime compiled components
Examples
# Template with layout features
template = ~S'''
<!DOCTYPE html>
<html>
<head>
<title><%= @title %></title>
<%= for link <- @resource_links do %>
<link rel={link.rel} href={link.href}>
<% end %>
<%= for meta <- @seo.meta_tags do %>
<meta name={meta.name} content={meta.content}>
<% end %>
</head>
<body>
<header><%= render_header() %></header>
<main><%= @inner_content %></main>
<footer><%= @copyright_text %></footer>
</body>
</html>
'''
# Usage after compilation
html = MyLayout.render(%{
title: "My Page",
copyright_text: "© 2024"
}, "<p>Welcome to my site!</p>")Error Handling
Template compilation errors are caught and reported with:
- File context - Using generated file names for debugging
- Line numbers - Accurate error positioning
- Syntax highlighting - Clear error messages
Invalid template syntax will prevent module compilation with detailed feedback.
This function is called as the second stage in the compilation pipeline.
Creates the final module AST with all components assembled.
This is the final stage of the compilation pipeline that assembles all generated function ASTs into a complete module definition with proper imports and structure.
Parameters
{params, functions_ast}- Tuple containing:params- Layout configuration with:layout_moduletargetfunctions_ast- All compiled function ASTs from previous stages
Returns
Returns the complete module AST ready for compilation.
Generated Module Structure
The final module includes:
defmodule TargetModule do
require Logger
use MishkaCmsWeb, :live_view # whatever :web_module names
import MishkaCmsCore.Runtime.LiveViewHelpers, only: [component: 1, component: 2]
# Layout information functions
def __layout_information__() do ... end
def __layout_information__(key) when is_atom(key) do ... end
def __layout_information__(keys) when is_list(keys) do ... end
def __layout_information__(key) when is_binary(key) do ... end
# Render functions
def render(assigns) when is_map(assigns) do ... end
def render(assigns, inner_content) when is_map(assigns) do ... end
# Helper functions (if defined)
def helper_function_name(args) do ... end
endModule Dependencies
The generated module automatically includes:
- Logger - For debugging and error reporting
- The configured
:web_module- Phoenix LiveView functionality - MishkaCmsCore.Runtime.LiveViewHelpers - Runtime component access
These imports enable the layout to:
- Use LiveView features like
~Hsigil - Access Phoenix.Component functionality
- Call runtime-compiled MishkaCMS components
- Log errors and debug information
Example Generated Module
defmodule MyApp.Layouts.Main do
require Logger
use MishkaCmsWeb, :live_view # whatever :web_module names
import MishkaCmsCore.Runtime.LiveViewHelpers, only: [component: 1, component: 2]
def __layout_information__() do
%{
layout: %{title: "Main Layout", path: "/layouts/main"},
private: %{site: "main", format: :heex},
acl: %{permissions: []},
extra: nil
}
end
def render(assigns) when is_map(assigns) do
# Compiled template code here
end
def format_title(title, site_name) do
# Helper function code here
end
endIntegration Points
The module structure ensures compatibility with:
- Phoenix LiveView - Standard LiveView module pattern
- MishkaCMS Runtime - Component and layout system integration
- Phoenix Components - Full component library access
- Elixir OTP - Standard module loading and hot code swapping
Compilation Process
After this function returns the AST:
ast = create_module_ast({params, functions_ast})
{:ok, module} = MishkaCmsCore.Runtime.Compilers.ModuleCompiler.compile_module(ast)The module is then available for use throughout the application.
This is the final stage in the compilation pipeline and produces the complete module ready for runtime compilation and usage.
Creates compilation environment for template processing.
Builds a macro environment with proper imports and context for HEEx template compilation. This environment determines what modules and functions are available within compiled templates.
Returns
Returns a Macro.Env struct configured for layout template compilation.
Environment Configuration
The environment includes:
- Base Environment - Standard Elixir compilation environment
- Dynamic Imports - Configurable module imports for templates
- Error Handling - Graceful handling of missing import modules
Current Import Strategy
Currently, the imports list is empty ([]) but the infrastructure supports
dynamic import addition. Future enhancements can include:
# Example of potential imports
imports = [
Phoenix.Component,
Phoenix.HTML,
MyApp.HelperModules.DateHelpers,
MyApp.HelperModules.TextHelpers
]Import Processing
Each import is processed with:
- Module availability check - Verifies module is loaded
- Safe import definition - Uses
Macro.Env.define_import/3 - Error logging - Warns about failed imports without stopping compilation
- Graceful degradation - Continues compilation even if some imports fail
Error Handling
Import failures are handled gracefully:
- Module not loaded - Logs warning and continues
- Import definition error - Logs error details and continues
- Compilation continues - Failed imports don't prevent template compilation
Usage in Template Compilation
This environment is passed to Phoenix.LiveView.TagEngine.compile/2 to provide
proper context for template compilation:
env = make_env()
opts = [caller: env, ...]
Phoenix.LiveView.TagEngine.compile(template, opts)Performance Considerations
- Environment creation is lightweight
- Import processing is done once during compilation
- Failed imports have minimal performance impact
- Environment is reused across template compilation calls