Crate stpl [] [src]

stpl - Super template library for Rust

stpl is a plain-Rust-only template library with some neat properties and features.

Main idea

In stpl there are no magic macros or DSLs, and no clunky text-files with weird syntax. Everything is just normal, easy to understand Rust code.

Let's take a look at a real-life example from the pilot project: an HTML base-skeleton template for a Bootstrap-based UI.

Be careful when using this code, it's not being tested!
pub fn base<C: Render + 'static>(data: &Data, content: C) -> impl Render {
    (
        doctype("html"),
        html((
            head((
                meta.charset("utf-8"),
                meta.name("viewport").content("width=device-width, initial-scale=1, shrink-to-fit=no"),
                meta.name("description").content(""),
                meta.name("author").content("Dawid Ciężarkiewicz"),
                title(data.title.clone()),
                (
                    link.rel("icon").href("/static/favicon.ico"),
                    link.rel("stylesheet").href("/static/theme/flatly/bootstrap.min.css"),
                    link.rel("stylesheet").href("/static/theme/starter-template.css"),
                )
            )),
            body((
                navbar(data),
                main
                    .id("main")
                    .role("main")
                    .class("container mb-5")(
                    content,
                ),
                (
                script.src("https://code.jquery.com/jquery-3.2.1.min.js").crossorigin("anonymous"),
                script.src("https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js")
                    .integrity("sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh")
                    .crossorigin("anonymous"),
                script.src("https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js")
                    .integrity("sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ")
                    .crossorigin("anonymous"),
                script.type_("text/javascript")(
                    raw(include_str!("white-icon.js"))
                ),
                )
            ))
        ))
    )
}

It is just a function. There is no magic, no macros, no textfiles involved. The whole template was formatted with rustfmt just like a normal Rust code.

The function accepts arguments:

  • data: Data containing information how to "fill the blanks", and
  • content: Render - sub-template value that will be used as main page content.

The function returns Render value that can be rendered as a string or bytes, or composed with other templates. The value is basically a one big tuple nesting many other Render values. Render is implemented for many standard types, can be implemented for new types or can be generated using functions/closures.

Users are free to use any Rust language primitives to generate their templates and structure relationship between them in any way that suits them.

Dynamic rendering

While stpl generates Rust code and does not invole "runtime parsing", it supports doing the actual rendering in a separate process, thus hot-swapping the templates at runtime. This is very useful for speeding up development.

The basic mechanism is:

  • serialize the template data, and send it to a child process
  • read the rendered template back from the child

In the child process:

  • identify the template to use,
  • read the serialized data from the stdio and deserialize it
  • render the template and output it to stdout

In this scheme the binary for parent and child processes can be the same (see render_dynamic_self) or different (see `render_dynamic).

Using the same binary is more convenient. Using separate binaries requires structuring the project in a certain way, but can greatly improve iteration time.

The following is an exerpt from Cargo.toml to support dynamic rendering in a separate binary:


[[bin]]
name = "template"
path = "src/main_template.rs"

[[bin]]
name = "webapp"
path = "src/main.rs"

These two programs share many modules (eg. templates and data structures), but main_template does not have to include any heavy-duty libraries like rocket, diesel and similar, thus compiles much faster.

In our tests it takes 11.4 secs to build the main webapp in debug mode, while recompiling all templates is much faster:

$ cargo build --bin template
Compiling webapp v0.1.0 (file:///home/dpc/lab/rust/webapp/web)
Finished dev [unoptimized + debuginfo] target(s) in 1.04 secs

Pros

  • robust: template generation can reuse any existing code and data structures
  • convenient: Rust tooling can work with plain-Rust-templates just like any other code; rustfmt takes care of formatting, typos result in normal error messages etc.
  • fast: the compiler optimizes the template code to essential logic necessary to write-out the rendered template data to the IO; there is no parsing involved
  • fast iteration: with dynamic loading it's possible to reload templates without waiting for Rust compiler to build the whole app

Cons

  • nightly-only: This library relies on some unstable features (mostly impl trait)
  • immature and incomplete: This library is still work in progress, and will mature with time.

Where to start

You are most probably interested in reading html module documentation

Help

Please see ./playground subdirectory for example usage.

Modules

html

HTML rendering

Structs

Fn

Use to wrap closures with

HandleDynamic

handle_dynamic handle

RawRenderer

A Renderer that does not escape anything it renders

Enums

DynamicError

Constants

EXIT_CODE_FAILED

Exit code used by the dynamic template binary on failure

EXIT_CODE_NOT_FOUND

Exit code used by the deyamic template binary when template key was not found

EXIT_CODE_SUCCESS

Exit code used by the dynamic template binary on success

Traits

Render

A value that can be rendered - part or a whole template

Renderer

Rendering logic responsible for string escaping and such.

Template

A whole template that knows how to render itself

TemplateExt

Convinience methods for Template

Functions

handle_dynamic