P2 is a templating engine for dynamically producing HTML. P2 templates are expressed as Ruby procs, leading to easier debugging, better protection against HTML injection attacks, and better code reuse.
P2 templates can be composed in a variety of ways, facilitating the usage of layout templates, and enabling a component-oriented approach to building complex web interfaces.
In P2, dynamic data is passed explicitly to the template as block arguments, making the data flow easy to follow and understand. P2 also lets developers create derivative templates using full or partial parameter application.
P2 includes built-in support for rendering Markdown (using Kramdown). P2 also automatically escapes all text emitted in templates.
P2 features:
- Express HTML using plain Ruby procs.
- Automatic compilation for super-fast execution (up to 2X faster than ERB templates).
- Deferred rendering using defer.
- Template composition (for uses such as layouts).
- Automatic conversion of backtraces for exceptions occurring while rendering, in order to point to the correct spot in the original template code.
- Installing P2
- Basic Usage
- Adding Tags
- Tag and Attribute Formatting
- Escaping Content
- Template Parameters
- Template Logic
- Template Blocks
- Template Composition
- Parameter and Block Application
- Higher-Order Templates
- Layout Template Composition
- Emitting Raw HTML
- Emitting a String with HTML Encoding
- Emitting Markdown
- Deferred Evaluation
- API Reference
Note: P2 requires Ruby version 3.4 or newer.
Using bundler:
Or manually:
In P2, an HTML template is expressed as a proc:
Rendering a template is done using #render:
Tags are added using unqualified method calls, and can be nested using blocks:
Tag methods accept a string argument, a block, or no argument at all:
Tag methods also accept tag attributes, given as a hash:
A true attribute value will emit a valueless attribute. A nil or false attribute value will emit nothing:
An attribute value given as an array will be joined by space characters:
P2 does not make any assumption about what tags and attributes you can use. You can mix upper and lower case letters, and you can include arbitrary characters in tag and attribute names. However, in order to best adhere to the HTML specs and common practices, tag names and attributes will be formatted according to the following rules, depending on the template type:
-
HTML: underscores are converted to dashes:
-> { foo_bar { p 'Hello', data_name: 'world' } }.render #=> '<foo-bar><p data-name="world">Hello</p></foo-bar>'
If you need more precise control over tag names, you can use the #tag method, which takes the tag name as its first parameter, then the rest of the parameters normally used for tags:
P2 automatically escapes all text content emitted in a template. The specific escaping algorithm depends on the template type. For HTML templates, P2 uses escape_utils, specifically:
- HTML: escape_utils.escape_html
In order to emit raw HTML, you can use the #emit method as described below.
In P2, parameters are always passed explicitly. This means that template parameters are specified as block parameters, and are passed to the template on rendering:
Templates can also accept named parameters:
Since P2 templates are just a bunch of Ruby, you can easily embed your view logic right in the template:
Templates can also accept and render blocks by using emit_yield:
P2 makes it easy to compose multiple templates into a whole HTML document. A P2 template can contain other templates, as the following example shows.
In addition to using templates defined as constants, you can also use non-constant templates by invoking the #emit method:
Parameters and blocks can be applied to a template without it being rendered, by using #apply. This mechanism is what allows template composition and the creation of higher-order templates.
The #apply method returns a new template which applies the given parameters and or block to the original template:
P2 also lets you create higher-order templates, that is, templates that take other templates as parameters, or as blocks. Higher-order templates are handy for creating layouts, wrapping templates in arbitrary markup, enhancing templates or injecting template parameters.
Here is a higher-order template that takes a template as parameter:
The inner template can also be passed as a block, as shown above:
One of the principal uses of higher-order templates is the creation of nested layouts. Suppose we have a website with a number of different layouts, and we'd like to avoid having to repeat the same code in the different layouts. We can do this by creating a default page template that takes a block, then use #apply to create the other templates:
Raw HTML can be emitted using #emit:
To emit a string with proper HTML encoding, without wrapping it in an HTML element, use #text:
Markdown is rendered using the Kramdown gem. To emit Markdown, use #emit_markdown:
Kramdown options can be specified by adding them to the #emit_markdown call:
You can also use P2.markdown directly:
The default Kramdown options are:
The deafult options can be configured by accessing P2.default_kramdown_options, e.g.:
Deferred evaluation allows deferring the rendering of parts of a template until the last moment, thus allowing an inner template to manipulate the state of the outer template. To in order to defer a part of a template, use #defer, and include any markup in the provided block. This technique, in in conjunction with holding state in instance variables, is an alternative to passing parameters, which can be limiting in some situations.
A few use cases for deferred evaulation come to mind:
- Setting the page title.
- Adding a flash message to a page.
- Using templates that dynamically add static dependencies (JS and CSS) to the page.
The last use case is particularly interesting. Imagine a DependencyMananger class that can collect JS and CSS dependencies from the different templates integrated into the page, and adds them to the page's <head> element:
HTML templates include a few HTML-specific methods to facilitate writing modern HTML:
- html5 { ... } - emits an HTML 5 DOCTYPE (<!DOCTYPE html>)
- import_map(root_path, root_url) - emits an import map including all files matching <root_path>/*.js, based on the given root_url
- js_module(js) - emits a <script type="module"> element
- link_stylesheet(href, **attributes) - emits a <link rel="stylesheet" ...> element
- script(js, **attributes) - emits an inline <script> element
- style(css, **attributes) - emits an inline <style> element
- versioned_file_href(href, root_path, root_url) - calculates a versioned href for the given file
The API reference for this library can be found here.
.png)


