xeronuro
11/19/2012 - 12:41 AM

Shows how to register filters and how to composite templates, css, and js files.

Shows how to register filters and how to composite templates, css, and js files.

package Mojolicious::Plugin::MicroPipeline::CSSCompressor;
use Mojo::Base 'Mojolicious::Plugin';

use CSS::Compressor 'css_compress';

sub register {
  my ($self, $app) = @_;

  # Register "css_compressor" filter
  $app->filter(css_compressor => sub { css_compress shift });
}

1;
package Mojolicious::Plugin::MicroPipeline;
use Mojo::Base 'Mojolicious::Plugin';

use Mojo::Asset::Memory;
use Mojo::Home;
use Mojo::Loader;
use Mojo::Util 'md5_sum';

sub register {
  my ($self, $app) = @_;

  # List all static files
  my @files
    = map { @{Mojo::Home->new($_)->list_files} } @{$app->static->paths};
  push @files,
    map { keys %{Mojo::Loader->new->data($_)} } @{$app->static->classes};

  # Register "filter" helper
  my %filters;
  $app->helper(
    filter => sub {
      my ($self, $name, $cb) = @_;
      $filters{$name} = $cb;
    }
  );

  # Register "asset" helper
  my %assets;
  $app->helper(
    asset => sub {
      my ($self, $name, $sources) = (shift, shift, shift);

      # Generate "link" or "script" tag
      unless ($sources) {
        my $checksum = $assets{$name}{checksum};
        $name =~ /^(.+)\.(\w+)$/;
        return $self->stylesheet("/$1.$checksum.$2") if $2 eq 'css';
        return $self->javascript("/$1.$checksum.$2");
      }

      # Concatenate
      my $asset = '';
      for my $source (@$sources) {

        # Glob support
        $source = quotemeta $source;
        $source =~ s/\\\*/[^\/]+/;

        # Check all files
        for my $file (@files) {
          next unless $file =~ /^$source$/;
          $asset .= $self->app->static->file($file)->slurp;
        }
      }

      # Filter
      for my $filter (@_) { $asset = $filters{$filter}->($asset) }

      # Store source with current checksum
      $assets{$name} = {source => $asset, checksum => md5_sum($asset)};
    }
  );

  # Asset dispatcher
  $app->hook(
    before_dispatch => sub {
      my $self = shift;

      # Match asset path with checksum
      return
        unless $self->req->url->path =~ /^\/?(.+)\.([[:xdigit:]]+)\.(\w+)$/;
      return unless my $asset = $assets{"$1.$3"};

      # Serve asset
      $self->app->log->debug(qq/Serving asset "$1.$3" with checksum "$2"./);
      $self->app->static->serve_asset($self,
        Mojo::Asset::Memory->new->add_chunk($asset->{source}));
      $self->tap(sub { shift->stash('mojo.static' => 1) })->rendered;
    }
  );
}

1;
use Mojolicious::Lite;

plugin 'MicroPipeline';
plugin 'MicroPipeline::CSSCompressor';

app->asset('app.js' => ['one.js', 'js/two.js']);
app->asset('app.css' => ['stylesheets/*.css'] => 'css_compressor');

get '/' => 'index';

app->start;
__DATA__

@@ index.html.ep
% title 'Hello!';
% layout 'default';
<div id="hello">Hello Mojo!</div>

@@ layouts/default.html.ep
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    %= asset 'app.css'
    %= asset 'app.js'
  </head>
  <body><%= content %></body>
</html>

@@ one.js
alert('One!');

@@ js/two.js
alert('Two!');

@@ stylesheets/one.css
div {
  color: #fff;
}

@@ stylesheets/two.css
body {
  background-color: #000;
}

@@ stylesheets/three.css
#hello {
  font: 0.9em 'Helvetica Neue', Helvetica, sans-serif;
}

@@ stylesheets/four.css
body {
  margin: 0;
}

@@ stylesheets/five.css
#hello {
  line-height: 1.5em;
}