NAME

App::SlimPacker - PPI-based minifier and fatpack-style bundler for standalone Perl scripts

VERSION

Version 0.04

SYNOPSIS

use App::SlimPacker qw(process module_deps inline_plugins);

# Minify Perl source (strip comments/POD, collapse whitespace, rename vars)
my $minified = process($source, rename => 1);

# Extract static dependencies from source
my @deps = module_deps('use App::Foo; require App::Bar;');

# Inline Module::Pluggable plugin list
my $boot = inline_plugins($program, \%classes);

DESCRIPTION

App::SlimPacker provides a PPI-based minifier and bundling helpers for building standalone Perl scripts. It is used by the slimpack CLI to assemble fatpacked, minified executables.

The minifier (process) strips comments and POD, collapses whitespace, and optionally renames my variables to short names while respecting string interpolation, regexes, heredocs, and readlines. local/our declarations are left untouched because they may be package globals reachable by a fully-qualified $PKG::name reference elsewhere. It can also apply an optional rewrite pass that shortens keywords and operators (foreach -> for, m/.../ -> /.../ after =~/!~, trailing ; before }, += 1 -> ++, $x = $x OP $y -> $x OP= $y, builtin call parens); see "process($source, %options)". The rewrite pass is opt-in and self-contained so it can be disabled (rewrite = 0>) or removed from the pipeline entirely if it ever proves unsafe.

The bundling helpers resolve static dependencies, inline Module::Pluggable plugin lists, and build perl-style switch programs from -m/-M/-e/-E arguments.

Unlike fatpack, which copies bundled modules verbatim, the slimpack pipeline runs every module through the PPI minifier. Packing this Moo hello-world into a self-contained script:

package MyGreeter;
use Moo;
has name => (is => 'ro', default => sub { 'world' });
sub greet { my $self = shift; return "Hello, " . $self->name . "!\n"; }
package main;
print MyGreeter->new->greet;

with Moo on the module path:

fatpack pack helloworld.pl > helloworld.fatpack.pl
slimpack -o helloworld.slimpack.pl helloworld.pl

fatpack produced 294 KB across 9,929 lines, slimpack 59 KB across 27 lines; both print Hello, world! with no Moo and no PERL5LIB at run time. Sizes vary with the module set.

EXPORTS

Nothing is exported by default. All functions are available for import:

use App::SlimPacker qw(process module_deps);

FUNCTIONS

process($source, %options)

Minifies Perl source code using PPI. Returns the minified string.

Options:

rename => 0|1

Rename my variables to short names (a, b, ... aa, ...). Enabled by default. Set to 0 to keep variable names intact (useful for fatlib core modules).

rewrite => 0|1

Apply an additional, optional pass that rewrites code into shorter equivalent forms. Disabled by default. The rewrites are all value-preserving and built as a self-contained, removable step so the whole pass can be switched off (rewrite = 0>) or deleted (the _rewrite_doc method and its one call site in _minify_doc) if it ever causes problems.

Currently: foreach -> for; m/.../ -> /.../ when preceded by =~ or !~ (the m is optional with / delimiters); trailing ; before a closing } is dropped; a $x += 1; statement in a non-final position becomes $x++; (and -= 1> -> --); $x = $x OP $y; with a single-token RHS becomes $x OP= $y; for . + - * / %; and parentheses are dropped from statement-terminal builtin calls that take a variable argument (print($x), return($y), push(@a, $x)).

Variable renaming skips names used inside strings, regexes, heredocs, readlines, backticks, %KEEP names, ALL_CAPS names, and single-character names.

process_deps($source, %options)

Minifies and resolves dependencies in a single PPI parse. Returns ($minified, @deps) where $minified is what "process($source, %options)" would return for the same source and @deps is what "module_deps($source)" would return. Use it when both quantities are needed for the same file to avoid parsing it twice; this is what the bundler does for every reachable module.

minify_file($path, %options)

Reads the file at $path and runs it through "process($source, %options)" with the given options. Dies if the file cannot be read. Returns the minified string.

A leading #! shebang line is preserved: process strips it as a comment, and a minified script must stay runnable.

module_deps($source)

Returns a list of module names statically declared as dependencies in the given Perl source. Extracts modules from use, require, use base, use parent, and string-form require "Foo/Bar.pm". Pragmas (strict, warnings, lib, etc.) are skipped.

plugin_search_paths($program)

Extracts Module::Pluggable search paths from a program's use Module::Pluggable (...) statement. Returns a hashref of namespace => 1.

inline_plugins($program, \%classes)

Inlines a plugin class list into plugins() calls based on the Module::Pluggable search paths in $program and the available classes (hashref of Class::Name => 1). Classes are matched one level deep (Module::Pluggable's default), sorted. The use Module::Pluggable statement is removed so it never loads at runtime.

Returns the modified program text. Programs without Module::Pluggable or without a search_path are returned unchanged.

perl_switches(\@m, \@M, \@e, \@E)

Builds a Perl program string from -m/-M/-e/-E switch arguments (perl-binary style). Returns the program text with appropriate use statements prepended.

name_gen($n)

Returns a short variable name for the index $n: 0 -> a, 1 -> b, 25 -> z, 26 -> aa, etc.

needs_space($left_token, $right_token)

Returns 1 if a space is needed between two PPI tokens to prevent them from merging into a single token, 0 otherwise.

pack_string($literal)

Returns the shortest exact Perl string literal for an arbitrary byte string. Used by the bundler to embed minified module sources, where B::perlstring would double-quote and escape every sigil ("\$x", "\@_"), inflating the bundle by ~15%. pack_string never uses perlstring; content is escaped in place inside quote literals, keeping $ @ % " raw.

Two literal families are priced so the cheaper wins:

  • '...' -- cost 2 (delimiters) + backslash occurrences + single-quote occurrences, since both ' and \ must be escaped;

  • a q<delim> literal -- delimiter chosen from ^ ~ | ? , ; ! # & - + * / % :, the candidate occurring fewest times in the content. Cost 3 + backslash occurrences + delimiter occurrences, since backslashes and the delimiter character must be escaped. = is excluded (a q=...= literal breaks when the content contains a comparison like = >), as are < and > (paired delimiters already close on >, so q<< ... > > cannot nest); every candidate above is validated safe with escaping.

' occurs far more often in code than ^ or any other candidate, so the q<delim> form wins as soon as the content holds a quote, and wins ties for the same reason. No candidate needs to be absent: its occurrences are simply escaped (\^). The returned literal always evals back to the exact input bytes.

AUTHOR

Nicolas Mendoza, <mendoza at pvv.ntnu.no>

LICENSE AND COPYRIGHT

This software is licensed under the Artistic License 2.0. See the LICENSE file in this distribution for the full text.

SEE ALSO

slimpack, PPI, App::FatPacker