问题
Is it possible to use the Mojolicious template system to build a static website?
I'm trying to use a (skeleton) script like this:
use Mojo::Template;
use Mojolicious::Plugin::DefaultHelpers;
use Mojolicious::Plugin::TagHelpers;
my $mt = Mojo::Template->new;
print $mt->render_file('index.html.ep');
Where index.html.ep
is like this:
% layout 'default';
This is a foo
However I get an error nessage:
String found where operator expected at index.html.ep line 1, near "layout 'default'"
(Do you need to predeclare layout?)
syntax error at index.html.ep line 1, near "layout 'default'"
1: % layout 'default';
2: This is a foo
Obviously, if I omit % layout 'default';
all is good, but being able to reuse snippets and layout is the whole point.
I know I could use Template Toolkit or some other templating system, but I want to avoid the cognitive friction of using multiple systems if at possible.
I'm also aware that I could start up mojolicious as a server and wget all the pages, but it seems overkill.
Any help here?
回答1:
You can use Mojo templates outside of the Mojolicious web framework – I used to do that to render static pages for my blog. However, the Mojo::Template
does not come with the normal helpers by default. Instead, the rest of Mojolicous injects variables and helpers into the template.
For my blog, I decided to implement my own helper system. I'll describe my solution in the rest of this answer. Mojo may have changed in the meantime, and may prefer some different solution.
I modelled a template as a pair of a stash reference and the Mojo::Template object. Each Template is compiled into its own package. Later, we can inject temporary values into the stash reference and communicate values to the outside. A helper is a closure of a specific stash ref, so it can access these values without using an explicit parameter.
Here's how templates are compiled:
package AMON::Blog::TemplateCollection;
sub add_template($self, $name, $source) {
state $namespace_id = 0;
my $namespace = Package::Stash->new(
__PACKAGE__ . '::Namespace::' . ++$namespace_id);
my $template = Mojo::Template->new(
name => $name,
namespace => $namespace->name,
auto_escape => 1,
tag_start => '{{',
tag_end => '}}',
);
# enter the helpers into the namespace
my $stash_ref = \{};
while (my ($name, $code) = each %{ $self->helpers }) {
$namespace->add_symbol('&' . $name => $code->($stash_ref));
}
$template->parse($source);
$self->templates->{$name} = {
stash_ref => $stash_ref,
template => $template
};
return;
}
Here is a layout
helper that writes the requested layout into a stash variable:
layout => sub ($stash_ref) {
return sub ($name, %args) {
if (my $existing = $$stash_ref->{layout}) {
croak sprintf q(Can't change layout from "%s" to "%s"), $existing->{name}, $name;
}
$$stash_ref->{layout} = { name => $name, args => \%args };
};
},
The outer sub is only used to close over the $stash_ref
, and is executed during template compilation above.
To render the template, we supply temporary stash values, then process the Mojo::Template. If the stash contains a layout argument, we recurse to render the layout template with the current template's output as content:
sub render($self, $name, %args) {
my $template = $self->templates->{$name}
// croak qq(Unknown template "$name");
my ($stash_ref, $template_object) = @$template{qw/stash_ref template/};
$$stash_ref = {
name => $name,
layout => undef,
args => \%args,
};
my $result = $template_object->process();
my $layout_args = $$stash_ref->{layout};
$$stash_ref = undef;
if (blessed $result and $result->isa('Mojo::Exception')) {
die $result;
}
if ($layout_args) {
my $name = $layout_args->{name};
my $args = $layout_args->{args};
return $self->render($name, %$args, content => $result);
}
return $result;
}
This approach is not terribly elegant, but it works without having to pull in all the rest of Mojolicious (particularly controllers, which are pointless for a static site). Some time later I switched to a different template engine that supports template inheritance out of the box, without such extensive workarounds.
回答2:
Adding another (minimal) way to do this:
use Mojolicious;
my $m = Mojolicious->new->log(Mojo::Log->new);
my $r = $m->renderer;
push @{$r->paths}, './templates';
my $c = Mojolicious::Controller->new->app($m);
my ($output, $format) = $r->render($c, { template => 'index' });
print $output
Iterating this through a number of templates, and sending the output to appropriately named files should be straightforward.
回答3:
If you run your Mojo app via Plack
then you can use https://metacpan.org/pod/wallflower to create a static site from your app.
来源:https://stackoverflow.com/questions/49230849/can-i-use-mojolicious-to-build-a-static-site