Skip to content

Template::Nest::Fast is a high-performance template engine module for Raku

License

Notifications You must be signed in to change notification settings

andinus/TemplateNestFast

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Template::Nest::Fast

Documentation

Template::Nest::Fast is a high-performance template engine module for Raku, designed to process nested templates quickly and efficiently. This module improves on the original Template::Nest module by caching the index of positions of variables, resulting in significantly faster processing times.

For more details on Template::Nest visit: https://metacpan.org/pod/Template::Nest

Note: This module was created by me as a proof-of-concept to benchmark against Template::Nest::XS. Tom Gracey (virtual.blue) is currently sponsoring for the development of this module. He authored Template::Nest originally in Perl 5.

Note: Template::Nest::XS is the recommended module for use in production. Considerable effort has gone into optimising Template::Nest::XS, and it is now blindingly fast. Make sure to use the latest version (v0.1.9 at the time of writing), as some instabilities have recently been resolved.

As a pure Raku version, you may also find this module (Template::Nest::Fast) useful for development/testing purposes - as e.g. a full stack trace can be obtained from it (unlike the XS version which is more of a black box). There may be other circumstances where this module is useful. However be aware it is approx 25 times slower than the XS version.

It is not recommended to use the original pure Raku version of Template::Nest. This module was a line by line rewrite of the Perl 5 module - which unfortunately turned out to be far too slow to be practical.

Options

  • Note: The options have their hypen counterparts. For example, comment_delims is now comment-delims. However, for compatibility with other ::Nest versions, underscored options are supported on object creation.

Compatibility Progress:

[X] comment_delims
[X] defaults
[X] defaults_namespace_char
[X] die_on_bad_params
[ ] escape_char             - Won't be implemented
[X] fixed_indent
[X] name_label
[X] show_labels
[X] template_dir
[X] template_ext
[X] token_delims
  • Note: escape_char has been replaced with token-escape-char.
  • Note: cache-template option has been added and is enabled by default.
  • name-label (default TEMPLATE): Represents the label used for identifying the template name in the hash of template variables.
  • template-dir: IO object representing the directory where the templates are located.
  • cache-template (default: True): If True then the whole template file is cached in memory, this improves performance.
  • die-on-bad-params (default: False): If True, then an attempt to populate a template with a variable that doesn’t exist (i.e. name not found in template file) results in an error.

    Note that it allows to leave variables undefined (i.e. name found in template file but not defined in template hash). Undefined variables are replaced with empty string. This is for compatibility with Template::Nest (Raku).

    You might want to keep this enabled during development & testing.

  • show-labels (default: False): If True, an string is appended to every rendered template which is helpful in identifying which template the output text came from. This is useful in development when you have many templates.

    Example:

    <!-- BEGIN 00-simple-page -->
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Simple Page</title>
      </head>
      <body>
        <p>A fairly simple page to test the performance of Template::Nest.</p>
        <p>Simple Variable</p>
        <!-- BEGIN 01-simple-component -->
    <p>Simple Variable in Simple Component</p>
    <!-- END 01-simple-component -->
      </body>
    </html>
    <!-- END 00-simple-page -->
        

    Here, ‘BEGIN’ & ‘END’ blocks have been added because show-labels was set to True.

    If you’re not templating HTML and still want labels, you can set comment-delims.

  • comment-delims (default: ['<!--', '-->']): Use this in conjunction with show-labels. Expects a 2 element array. Example, for templating JS you could do:
    my $nest-alt = Template::Nest::Fast.new(
        :$template-dir, :show-labels, comment-delims => ['/*', '*/']
    );
        

    Example output:

    /* BEGIN js-file */
    ...
    /* END js-file */
        

    You can set the second comment token as an empty string if the language you are templating does not use one. Example, for templating Raku you could do:

    my $nest-alt = Template::Nest::Fast.new(
        :$template-dir, :show-labels, comment-delims => ['#', '']
    );
        

    Example output:

    # BEGIN raku-file
    ...
    # END raku-file */
        
  • template-extension (default: html): get/set the template extension. This is so you can save typing your template extension all the time if it’s always the same. There is no reason why this templating system could not be used to construct any other type of file (or why you could not use another extension even if you were producing html). Example, to manipulate JavaScript files, this will look for 30-main.js in $template-dir:
    my $nest-js = Template::Nest::Fast.new: :$template-dir, :template-extension('js');
    my %simple-page-js = %(
        TEMPLATE => '30-main',
        var => 'Simple Variable',
    );
        

    Or if you have an empty template-extension, this will look for 30-main.html in $template-dir:

    my $nest = Template::Nest::Fast.new: :$template-dir, :template-extension('');
    my %simple-page-js = %(
        TEMPLATE => '30-main.html',
        var => 'Simple Variable',
    );
        
  • fixed-indent (default: False): Intended to improve readability when inspecting nested templates. For example, consider these templates:

    wrapper.html:

    <div>
        <!--% contents %-->
    </div>
        

    photo.html:

    <div>
        <img src='/some-image.jpg'>
    </div>
        

    Output without fixed-indent:

    <div>
        <div>
        <img src='/some-image.jpg'>
    </div>
    </div>
        

    Output with fixed-indent:

    <div>
        <div>
            <img src='/some-image.jpg'>
        </div>
    </div>
        
  • token-delims (default: ['<!--%', '%-->']): Set the delimiters that define a token (to be replaced). For example, setting token-delims to ['<%', '%>'] would mean that render will now recognize and interpolate tokens in the format:
    <% variable %>
        
  • token-escape-char (default: empty string): On rare occasions you may actually want to use the exact character string you are using for your token delimiters in one of your templates. For example, here render is going to consider this as a token and remove it:
    did you know we are using token delimiters <!--% and %--> in our templates?
        

    To include the token, escape it with token-escape-char set to (\):

    did you know we are using token delimiters \<!--% and %--> in our templates?
        

    Set it to an empty string to disable the behaviour.

  • defaults: Provide a hash of default values that are substituted if template hash does not provide a value. For example, passing this defaults hash:
    my $nest = Template::Nest::Fast.new(
        :$template-dir,
        defaults => %(
            variable => 'Simple Variable',
            space => %(
                inside => 'A variable inside a space.'
            )
        ),
    );
        

    This $nest will first look for variable in template hash, then in %defaults hash. If no value is found then namespaced defaults are considered (look defaults-namespace-char).

  • defaults-namespace-char (default: .): Say you want to namespace values in %defaults hash to differentiate parameters coming from template hash and chose to prefix those variables like so:
    <!--% config.title %--> - <!--% config.description %-->
        

    You can pass a defaults like:

    %(
        "config.title" => "Title",
        "config.description" => "Description"
    )
        

    However, writing config. repeatedly is a bit effortful, so you can do the following:

    %(
        config => %(
            "title" => "Title",
            "description" => "Description"
        )
    )
        

    Note: To disable this behaviour set defaults-namespace-char to an empty string.

  • advanced-indexing (default: False): When enabled, ::Fast stores the timestamp of template file index and if the file on disk is newer, it re-indexes the file. It also indexes files that are present on disk but weren’t indexed when ::Fast was initialized.

Methods

  • render: Converts a template structure to output text. See Example for details.

Example

Templates: templates/00-simple-page.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Simple Page</title>
  </head>
  <body>
    <p>A fairly simple page to test the performance of Template::Nest.</p>
    <p><!--% variable %--></p>
    <!--% simple_component %-->
  </body>
</html>

templates/01-simple-component.html:

<p><!--% variable %--></p>

Simple template hash

This is a simple example that injects a variable in a template. We use another template as a component as well.

use Template::Nest::Fast;

# Create a nest object.
my $nest = Template::Nest::Fast.new( template-dir => 'templates/'.IO );

# Declare template structure.
my %simple-page = %(
    TEMPLATE => '00-simple-page',
    variable => 'Simple Variable',
    simple_component => %(
        TEMPLATE => '01-simple-component',
        variable => 'Simple Variable in Simple Component'
    )
);

# Render the page.
put $nest.render(%simple-page);

Output:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Simple Page</title>
  </head>
  <body>
    <p>A fairly simple page to test the performance of Template::Nest.</p>
    <p>Simple Variable</p>
    <p>Simple Variable in Simple Component</p>
  </body>
</html>

Array of template hash

Array of template hash can be passed to render too.

$nest.render(
    [
        %( TEMPLATE => '01-simple-component',  variable => 'This is a variable' ),
        %( TEMPLATE => '01-simple-component',  variable => 'This is another variable' )
    ]
)

Output:

<p>This is a variable</p><p>This is another variable</p>

Array to template variable

Template variable can be a string, another template hash or an array too. The array itself can contain template hash, string or nested array.

my %simple-page-arrays = %(
    TEMPLATE => '00-simple-page',
    variable => 'Simple Variable',
    simple_component => [
                         # Hash passed.
                         %(
                             TEMPLATE => '01-simple-component',
                             variable => 'Simple Variable in Simple Component'
                         ),
                         # Can pass string as well.
                         "<strong>Another test</strong>",
                         # Or another level of nesting.
                         [
                             %(
                                 TEMPLATE => '01-simple-component',
                                 variable => 'Simple Variable in Simple Component'
                             ),
                             "<strong>Another nested test 2</strong>"
                         ]
                     ]
);

Output:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Simple Page</title>
  </head>
  <body>
    <p>A fairly simple page to test the performance of Template::Nest.</p>
    <p>Simple Variable</p>
    <p>Simple Variable in Simple Component</p><strong>Another test</strong><p>Simple Variable in Simple Component</p><strong>Another nested test 2</strong>
  </body>
</html>

News

v0.2.8 - 2023-06-27

  • Bug Fix: Earlier Seq was rendered as Str, now it is considered a List.

v0.2.6 - 2023-05-29

  • Improve error message for die-on-bad-params.
  • Add more tests for advanced-indexing & die-on-bad-params.
  • Handle template file vanishing errors.

    Handles errors where the template file has vanished.

  • Fixed a bug where ::Fast does not die on non-existent template file.

    This was introduced in v0.2.5 with advanced indexing option.

v0.2.5 - 2023-05-25

  • Add advanced indexing option.

    When enabled, ::Fast stores the timestamp of template file index and if the file on disk is newer, it re-indexes the file. It also indexes files that are present on disk but weren’t indexed when ::Fast was initialized.

v0.2.4 - 2023-05-22

  • Add support for passing array to render method.

    Template::Nest [Perl5] supports this.

    Basic Example:

    $nest.render(
        [
            %( TEMPLATE => '01-simple-component',  variable => 'This is a variable' ),
            %( TEMPLATE => '01-simple-component',  variable => 'This is another variable' )
        ]
    )
        

    Output:

    <p>This is a variable</p><p>This is another variable</p>
        
  • Add examples for passing array to render method and passing array to a template variable.

v0.2.3 - 2023-05-14

  • Add support for underscored options.

    This makes the module close to drop-in for projects that use other Nest versions with support for underscored options.

  • Add support for Str, nested List when parsing hash template values.

    While parsing hash template values, when we encounter a list we assume that all the elements will be Hash. This breaks that assumption and allows for the elements to be of type Hash, Str or even List.

    After this change, template hash like this will be supported:

    my %simple-page-arrays = %(
        TEMPLATE => '00-simple-page',
        variable => 'Simple Variable',
        simple_component => [
                             # Hash passed.
                             %(
                                 TEMPLATE => '01-simple-component',
                                 variable => 'Simple Variable in Simple Component'
                             ),
                             # Can pass string as well.
                             "<strong>Another test</strong>",
                             # Or another level of nesting.
                             [
                                 %(
                                     TEMPLATE => '01-simple-component',
                                     variable => 'Simple Variable in Simple Component'
                                 ),
                                 "<strong>Another nested test 2</strong>"
                             ]
                         ]
    );
        

v0.2.2 - 2023-05-07

  • Fixed failing tests.

v0.2.1 - 2023-05-04

  • Fixed parsing bug with non-string values in template hash

    Template hash with non-string values like:

    my %template = %(
        TEMPLATE => 'simple-template',
        count => 200 # will result in failure
    );
        

    failed to parse prior to v0.2.1, this has been fixed in v0.2.1.

  • Improve error message for invalid template hash.

v0.2.0 - 2023-05-02

  • Achieved options compatibility with Template::Nest (Raku).
  • Added several options:
    • cache-template
    • die-on-bad-params
    • show-labels
    • comment-delims
    • template-extension
    • fixed-indent
    • token-delims
    • token-escape-char
    • defaults
    • defaults-namespace-char
  • Note: It’s not backwards compatible with Template::Nest (Raku).

v0.1.0 - 2023-03-28

  • Initial Release.

See Also