plugins
Plugins
Template::HAML::Plugin is the public surface for bundling related hooks ā
visitors, tag transformers,
filters, and the markdown backend ā into a single
artifact that can be installed and removed atomically.
Why bundle?
Each hook registry exposes its own register-* / clear-* subs. They work
fine for one-off use, but a plugin author shipping several related hooks
needs every hook installed before the first render and every hook removed
on uninstall. The Plugin class makes that bookkeeping the framework's
responsibility:
installruns every registration sub in declaration order.uninstallremoves exactly the hooks this plugin registered, by name.clear-pluginsuninstalls every plugin in reverse-install order.
Declaring a plugin
use Template::HAML::Plugin;
my $bootstrap = Template::HAML::Plugin::Plugin.new(
:name<bootstrap-helpers>,
:visitors([
%( :name<inject-meta>, :handler(-> $tree { ... ; $tree }) ),
]),
:tag-transformers([
%( :name<card>, :handler(-> $node {
$node.object.name = 'div';
$node.object.attrs.push: 'class' => 'card';
$node;
}) ),
]),
:filters([
%( :name<upper>, :handler(-> Str $body, %locals --> Str { $body.uc }) ),
]),
:markdown-backend(-> Str $body --> Str { ... }),
);
install-plugin($bootstrap);
# ... render with hooks active
uninstall-plugin($bootstrap);Every visitor, tag-transformer, and filter entry must be a hash with :name
and :handler keys. Both are validated at Plugin.new time, so a malformed
manifest fails at construction rather than mid-render. Plugin-registered
visitors are always named (so uninstall can find them) ā anonymous visitors
are only available through Template::HAML::Visitor::register-visitor
directly.
Lifecycle guarantees
The plugin lifecycle is stable. These properties are public API and won't change without a major version bump:
Registration order is preserved. Inside one plugin, visitors run in the order they appear in the
:visitorslist. Across plugins, visitors run in plugin-install order, then per-plugin order. The same applies to filter and tag-transformer registration: later wins on name collision.Install is idempotent. Calling
install-plugin($p)on an already installed plugin is a no-op. The plugin doesn't appear twice ininstalled-plugins().Uninstall is idempotent. Calling
uninstall-plugin($p)on a not-installed plugin is a no-op.Uninstall is by name. Uninstalling a plugin clears the hooks it registered by name. If a different plugin (or direct registration) later overrode that name, uninstalling the original plugin removes the currently-registered handler for that name. Plugins that share hook names are co-operating, not isolated.
clear-plugins()uninstalls in reverse install order. Returns the number of plugins it removed.Hooks observe the same pipeline order as before: parse ā visitors ā tag transformers ā render. Filters resolve per
:nameline at render time. Tag transformers and visitors run before code generation, socompile-source-to-rakuand the on-disk cache reflect the transformed tree. Installing or uninstalling a plugin auto-invalidates the in-process direct-emit and compiled-&fncaches; call clear-compiled-cache if you also need the on-disk cached.rakumodartifacts rebuilt.
Inspecting installed plugins
my @plugins = installed-plugins(); # list of Plugin objects
my @names = installed-plugin-names(); # list of Str
my $maybe = installed-plugin('card'); # Plugin or type object
my Int $n = clear-plugins(); # uninstall all, return countinstalled-plugin($name) returns the Plugin type object (undefined) when
no plugin with that name is installed ā guard with .defined.
What is not a plugin
Render-time helpers ā those go through the
:contextobject. See Render context.Configuration overrides ā use
Template::HAML::Config. See Configuration.Per-template scope additions ā use
:%localsor:contextonrender.