Markdown Integration

HypertextTemplates.jl provides seamless integration with CommonMark.jl, allowing you to create components from Markdown files and mix Markdown content with your templates.

Prerequisites

The Markdown integration features are provided through Julia's package extension system, which means they become available automatically when CommonMark.jl is present in your environment. This design keeps HypertextTemplates lightweight for users who don't need Markdown support while providing seamless integration for those who do. Simply adding CommonMark.jl to your project enables all the Markdown-related functionality described in this guide.

using Pkg
Pkg.add("CommonMark")

The integration is provided through Julia's package extension system, so features are automatically available when CommonMark.jl is loaded.

Inline Markdown with CommonMark

Basic Usage

CommonMark.jl's cm string macro allows you to embed Markdown content directly within your HypertextTemplates components. The macro processes the Markdown at compile time and converts it to HTML, which is then wrapped in a SafeString to preserve the formatting. This approach combines the simplicity of Markdown for content authoring with the power of HypertextTemplates for structure and interactivity, making it ideal for documentation sites, blogs, or any content-heavy application.

using HypertextTemplates
using HypertextTemplates.Elements
using CommonMark

@component function article_with_markdown()
    @article {class = "prose"} begin
        @text CommonMark.cm"""
        # Markdown Heading
        
        This is a paragraph with **bold** and *italic* text.
        
        - List item 1
        - List item 2
        
        > A blockquote
        
        ```julia
        # Code block
        println("Hello from Markdown!")
        ```
        """
    end
end

# The Markdown is converted to HTML and rendered
html = @render @article_with_markdown

Interpolation in Markdown

CommonMark.jl supports Julia interpolation:

@component function dynamic_markdown(; user_name, item_count)
    @div {class = "content"} begin
        @text CommonMark.cm"""
        # Welcome, $(user_name)!
        
        You have **$(item_count)** items in your cart.
        
        $(item_count > 0 ? "Ready to checkout?" : "Start shopping!")
        """
    end
end

@render @dynamic_markdown {user_name = "Alice", item_count = 3}

Markdown File Components

The @cm_component macro creates components from Markdown files.

Basic File Component

The @cm_component macro transforms Markdown files into reusable HypertextTemplates components. This powerful feature allows you to maintain content in Markdown format while seamlessly integrating it into your component-based architecture. The macro reads the specified Markdown file, processes it with CommonMark, and creates a component that can be used just like any other HypertextTemplates component. This is particularly useful for static content pages, documentation, or any scenario where non-technical users need to contribute content.

# Create a component from a Markdown file
@cm_component about_page() = "content/about.md"
@deftag macro about_page end

# Use it like any other component
@render @div {class = "page"} begin
    @about_page
end

Components with Props

Pass props to Markdown files for dynamic content:

# Define component with props
@cm_component product_description(; name, price) = "templates/product.md"
@deftag macro product_description end

# product.md content:
# # $(name)
#
# **Price:** $$(price)

# Use the component
@render @product_description {
    name = "Premium Widget",
    price = 99.99,
}

Organizing Markdown Components

# Use relative paths from your module
@cm_component header() = joinpath(@__DIR__, "partials", "header.md")
@cm_component footer() = joinpath(@__DIR__, "partials", "footer.md")

# Or organize in a module
module MarkdownComponents
    using HypertextTemplates

    # Define all Markdown components
    @cm_component home() = "content/home.md"
    @cm_component about() = "content/about.md"
    @cm_component contact() = "content/contact.md"

    # Export tags if you want to use without qualification
    @deftag export macro home end
    @deftag export macro about end
    @deftag export macro contact end
end

# Use with module qualification
@render @MarkdownComponents.home

# Or if exported
using .MarkdownComponents
@render @home

Live Reloading with Revise.jl

When Revise.jl is available, Markdown file components automatically reload when the file changes.

Setup

using Revise
using HypertextTemplates
using CommonMark

# Define a Markdown component
@cm_component live_content() = "content/live.md"

# Edit content/live.md in your editor
# Changes are reflected immediately when you re-render
@render @live_content

Advanced Markdown Patterns

Markdown Layout Components

Create layout components that accept Markdown content:

@component function markdown_layout(; title, markdown_file)
    @html begin
        @head begin
            @title $title
            @style """
            .prose { max-width: 65ch; margin: 0 auto; }
            .prose h1 { color: #333; }
            .prose code { background: #f4f4f4; padding: 0.2em; }
            """
        end
        @body begin
            @div {class = "prose"} begin
                @<markdown_file
            end
        end
    end
end

# Use with Markdown components
@cm_component intro() = "intro.md"
@render @markdown_layout {title = "Introduction", markdown_file = intro}

Mixed Content

Combine Markdown with HTML components:

@component function blog_post(; meta, content_file)
    @article begin
        @header begin
            @h1 $meta.title
            @p {class = "meta"} begin
                "By " @strong $meta.author " on " @time $meta.date
            end
        end

        @div {class = "content prose"} begin
            @<content_file  # Markdown component
        end

        @footer begin
            @nav begin
                if !isnothing(meta.prev)
                    @a {href = meta.prev.url} "← " $meta.prev.title
                end
                if !isnothing(meta.next)
                    @a {href = meta.next.url} $meta.next.title " →"
                end
            end
        end
    end
end

Dynamic Markdown Loading

Load Markdown content dynamically:

@component function dynamic_docs(; path)
    # Validate path for security
    safe_path = validate_doc_path(path)

    if isfile(safe_path)
        # Read and render Markdown
        content = read(safe_path, String)
        html = CommonMark.html(content)
        @div {class = "documentation"} $(SafeString(html))
    else
        @div {class = "error"} begin
            @h1 "404 - Page Not Found"
            @p "The requested documentation page does not exist."
        end
    end
end

function validate_doc_path(path)
    # Security: Ensure path is within docs directory
    cleaned = normpath(joinpath("docs", path))
    if startswith(cleaned, "docs/") && endswith(cleaned, ".md")
        return cleaned
    else
        return ""
    end
end

CommonMark Configuration

Custom Rendering

Configure CommonMark parsing and rendering:

using CommonMark

# Create custom parser with extensions
parser = Parser()
enable!(parser, DollarMathRule())
enable!(parser, TableRule())
enable!(parser, FootnoteRule())

@component function enhanced_markdown(; content)
    # Parse with custom settings
    ast = parser(content)

    # Render to HTML
    html = html(ast)

    @div {class = "enhanced-content"} $(SafeString(html))
end

Best Practices

1. File Organization

Structure your Markdown files logically:

project/
├── src/
│   └── components.jl
├── content/
│   ├── pages/
│   │   ├── home.md
│   │   └── about.md
│   ├── blog/
│   │   └── posts/
│   └── docs/
│       ├── getting-started.md
│       └── api-reference.md

2. Props Documentation

Document props in your Markdown files:

<!-- product-template.md -->
<!-- Props: name (String), price (Float64), description (String) -->

# $(name)

**Price:** $$(price)

$(description)

3. Performance

Cache parsed Markdown for frequently accessed content:

const MARKDOWN_CACHE = Dict{String, String}()

@component function cached_markdown(; file)
    html = get!(MARKDOWN_CACHE, file) do
        content = read(file, String)
        CommonMark.html(content)
    end

    @div {class = "cached-content"} $(SafeString(html))
end

Summary

Markdown integration in HypertextTemplates.jl provides:

  • Inline Markdown with the CommonMark.jl cm string macro
  • File-based components with @cm_component
  • Full interpolation support in Markdown content
  • Live reloading with Revise.jl integration

This integration makes it easy to build content-heavy sites while maintaining the full power of HypertextTemplates.jl's component system.