Skip to content

Instantly share code, notes, and snippets.

@nikopol
Created February 10, 2012 18:24
Show Gist options
  • Save nikopol/1791459 to your computer and use it in GitHub Desktop.
Save nikopol/1791459 to your computer and use it in GitHub Desktop.
html/php minifier
#!/usr/bin/env perl
#html/php minifier - niko 2012
#use: ./minify < tof.php > ../index.php
use strict;
use warnings;
use JSON;
use LWP::UserAgent;
use MIME::Base64;
my $read = 0;
sub load {
my( $file, $pfx ) = @_;
$pfx = '' unless $pfx;
warn $pfx."loading $file\n";
open(FH, $file) or die "unable to read $file\n";
$read += -s $file;
if( wantarray ){
my @lines = <FH>;
close FH;
return @lines;
}
my $buf = '';
$buf .= $_ while <FH>;
close FH;
$buf;
}
sub imagedata {
my( $file, $pfx ) = @_;
$pfx = '' unless $pfx;
return $file if $file =~ /^(http|data\:)/i;
my $ext = ( $file =~ /\.([^\.]+)$/ ) ? $1 : '?';
my $raw = load $file, $pfx;
my $len = length $raw;
my $data = encode_base64($raw,'');
my $zip = length $data;
warn " | $file encoded from $len to $zip (",int(100*($len-$zip)/$len),"%)\n";
'data:image/'.$ext.';base64,'.$data;
}
sub css_minify {
my( $file, $pfx ) = @_;
$pfx = '' unless $pfx;
my @css = load $file, $pfx;
$pfx .= ' | ';
my $path = $file;
$path =~ s/[^\/]+$//g;
$path = './' unless $path;
my $len = 0;
my $mini = '';
for my $l ( @css ){
$len += 1+length($l);
$l =~ s/\r+//g; #remove \r
$l =~ s/(^\s+|\s*$)//g; #remove lead/end spaces
$l =~ s/\s*\:\s*/:/g; #remove spaces around :
$l =~ s/\;\s+/;/g; #remove spaces after ;
$l =~ s/\s*\{\s*/{/g; #remove spaces around {
$l =~ s/\s*\}\s*/}/g; #remove spaces around }
$l =~ s/\s+/ /g; #reduce multiple spaces
$l =~ s/\s+/ /g; #reduce multiple spaces
$l =~ s/;?\}\s*/}\n/g; #remove ; ending a block and add line return after a block
if( $l =~ m/url\(\'?\"?([^\)'"]+)\'?\"?\)/ ){
my $url = $1;
if( $url !~ /^(http|data\:)/i ){
my $b64 = imagedata $path.$url, $pfx;
$l =~ s/$url/$b64/;
}
}
$mini .= $l unless $l =~ m|^/\*.*\*/$| || !$l;
}
my $zip = length($mini);
warn "$pfx$file minified from $len to $zip (",int(100*($len-$zip)/$len),"%)\n";
split /\n+/,$mini;
}
sub js_minify {
my $file = shift;
my $js = load $file;
return split(/\r?\n+/,$js) if $file =~ /min\.js$/i;
my $len = length $js;
my $ua = LWP::UserAgent->new;
my $res = $ua->post(
'http://closure-compiler.appspot.com/compile', [
'output_info' => 'compiled_code',
'output_info' => 'errors',
output_format => 'json',
warning_level => 'quiet',
#compilation_level => 'advanced_optimizations',
js_code => $js,
]
);
die $res->status_line,"\n" unless $res->is_success;
my $c = from_json($res->decoded_content);
if(exists $c->{serverErrors}){
warn 'ERROR #',$_->{'code'},': ',$_->{error},"\n"
foreach @{$c->{serverErrors}};
die "break on error\n";
}
if($c->{errors} && @{$c->{errors}}){
warn $_->{error},' line ',$_->{lineno},' near ',$_->{line},"\n"
foreach @{$c->{errors}};
die "break on error\n";
}
if($c->{compiledCode}) {
$js = $c->{compiledCode};
$js =~ s/[\r\n]+//;
my $zip = length $js;
warn " | $file closured from $len to $zip (",int(100*($len-$zip)/$len),"%)\n";
} else {
warn " | $file not closured\n";
}
split /\r?\n+/,$js
}
warn "parsing stdin\n";
my $mini = '';
for my $l ( <STDIN> ) {
$read += length $l;
if( $l =~ /^(\s*)<script[^\>]+src=[\'\"]([^\'\"]+)[\'\"]/i ) {
my $pfx = $1;
my $src = $2;
$mini .= $src =~ /^http/i
? $l
: "$pfx<script>\n".
"$pfx\t".join("\n$pfx\t",js_minify($src))."\n".
"$pfx</script>\n";
} elsif( $l =~ /^(\s*)<link[^\>]+href=[\'\"]([^\'\"]+\.css)[\'\"]/i ) {
my $pfx = $1;
my $src = $2;
$mini .= $src =~ /^http/i
? $l
: "$pfx<style>\n".
"$pfx\t".join("\n$pfx\t",css_minify($src))."\n".
"$pfx</style>\n";
} elsif( $l =~ /=['"]([^'"]+\.(jpg|png|gif))[\'\"]/i ) {
my $file = $1;
if( -s $file < 32768 ){
my $data = imagedata $file;
$l =~ s/$file/$data/;
}
$mini .= $l;
} else {
$mini .= $l;
}
}
my $zip = length $mini;
print $mini;
warn "done from $read to $zip (",int(100*($read-$zip)/$read),"%)\n";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment