The new Dancer2 plugin system

The biggest milestone this year in Dancer2 was version 0.200000, which introduced the long-awaited, completely reworked Dancer2 plugin system.

The previous plugin system suffered several key issues, all of which are now resolved by this new plugin systems.

Plugins are now easy to write and are far more capable than they were before.

Your first plugin

When using the new plugin systems, you need only load a single class:

package Dancer2::Plugin::MyPlugin;
use strict;
use warnings;
use Dancer2::Plugin;

You now have a full object oriented framework, which means you have attributes:

has 'name' => (
    'is'      => 'ro',
    'default' => sub {'Sawyer'},
);

If we want to export any keywords, we only need to call the right keyword:

plugin_keywords('name');

We can also call DSL by using the dsl method. Our object oriented system will run the BUILD method as soon as a new plugin object is loaded:

sub BUILD {
    my $self = shift;

    $self->dsl->get( '/', sub {
        $self->dsl->template(
            'index',
            { 'name' => $self->name },
        );
    } );
}

Configuring your plugin

Your applications can now use the plugin, and even provide values for them:

# In your config.yml:
plugins:
    MyPlugin:
        name: "Yanick"

Then later you can reach them using the config attribute:

has 'name' => (
    'is'      => 'ro',
    'default' => sub {
        return config->{'name'} || 'Sawyer';
    },
);

However, note that configurations will not update the attributes once they are set.

Using other plugins

You can use additional plugins within your plugin - a big thorn in the side of plugin developers until this new system.

package Dancer2::Plugin::Foo;
use strict;
use warnings;
use Dancer2::Plugin;
use Dancer2::Plugin::Bar;

# You can now use keywords from Dancer2::Plugin::Bar

Hooks

You can provide your own hooks using the plugin_hooks. Users can register actions to these hooks and you can then execute these hooks from your application:

plugin_keywords('my_keyword');
plugin_hooks('my_hook');

sub my_keyword {
    my $self = shift;
    $self->execute_plugin_hook('my_hook');
}

You can also call hooks available in the application:

$self->app->execute_hook('on_route_exception');

Subclassing

You can subclass other plugins:

package Dancer2::Plugin::Subclass;
use strict;
use warnings;

# Get keywords
use Dancer2::Plugin;

# Extend
extends('Dancer2::Plugin::Parent');

# Export this keyword as well
plugin_keywords('keyword_from_parent');

# redefine it
sub keyword_from_parent {
    my ( $self, @args_from_user ) = @_;

    my @args = @args_from_user;
    if ( !@args ) {
        @args = ( 'our', 'default', 'args' );
    }

    # Call the parent keyword
    $self->SUPER::keyword_from_parent(@args);
}

1;

Utilize existing plugins

From version 0.205000, you can now easily locate additional plugins and use their abilities:

package Dancer2::Plugin::Special;
use strict;
use warnings;
use Dancer2::Plugin;

plugin_keywords('special');

sub special {
  my $self = shift;

  my $basic_plugin = $self->find_plugin('Dancer2::Plugin::Basic')
      or $self->dsl->send_error( 'Please load Dancer2::Plugin::Basic', 500 );

  my $basic = $basic_plugin->normal_keyword;
  return "Special $basic";
}

1;

Now a user needs to load both plugins, and they will work together.

package MyApp;
use Dancer2;
use Dancer2::Plugin::Basic;
use Dancer2::Plugin::Special;

Summary

All the details are available in Dancer2::Plugin. There is at least one extra feature I haven't covered, just waiting for you to find it. Here's a clue: Attributes exporting. :)

Now with the new plugin system in place, we can not only provide consistent and highly-capable plugins, but introduce new stable features in the future.

Keep in touch to see where it leads us!

Author

This article has been written by Sawyer X for the Perl Dancer Advent Calendar 2016.

Copyright

No copyright retained. Enjoy.

Sawyer X.